juc500 정상동작
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .run import cli_run, session_via_cli
|
||||
from .version import __version__
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"cli_run",
|
||||
"session_via_cli",
|
||||
]
|
||||
@@ -0,0 +1,95 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from timeit import default_timer
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import MutableMapping
|
||||
|
||||
from virtualenv.config.cli.parser import VirtualEnvOptions
|
||||
from virtualenv.run.session import Session
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def run(
|
||||
args: list[str] | None = None, options: VirtualEnvOptions | None = None, env: MutableMapping[str, str] | None = None
|
||||
) -> None:
|
||||
env = os.environ if env is None else env
|
||||
start = default_timer()
|
||||
from virtualenv.run import cli_run # noqa: PLC0415
|
||||
from virtualenv.util.error import ProcessCallFailedError # noqa: PLC0415
|
||||
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
try:
|
||||
session = cli_run(args, options, env=env)
|
||||
LOGGER.warning(LogSession(session, start))
|
||||
except ProcessCallFailedError as exception:
|
||||
print(f"subprocess call failed for {exception.cmd} with code {exception.code}") # noqa: T201
|
||||
print(exception.out, file=sys.stdout, end="") # noqa: T201
|
||||
print(exception.err, file=sys.stderr, end="") # noqa: T201
|
||||
raise SystemExit(exception.code) # noqa: B904
|
||||
except OSError as exception:
|
||||
if exception.errno == errno.EMFILE:
|
||||
print( # noqa: T201
|
||||
"OSError: [Errno 24] Too many open files. You may need to increase your OS open files limit.\n"
|
||||
" On macOS/Linux, try 'ulimit -n 2048'.\n"
|
||||
" For Windows, this is not a common issue, but you can try to close some applications.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
class LogSession:
|
||||
def __init__(self, session: Session, start: float) -> None:
|
||||
self.session = session
|
||||
self.start = start
|
||||
|
||||
def __str__(self) -> str:
|
||||
spec = self.session.creator.interpreter.spec
|
||||
elapsed = (default_timer() - self.start) * 1000
|
||||
lines = [
|
||||
f"created virtual environment {spec} in {elapsed:.0f}ms",
|
||||
f" creator {self.session.creator!s}",
|
||||
]
|
||||
if self.session.seeder.enabled:
|
||||
lines.append(f" seeder {self.session.seeder!s}")
|
||||
path = self.session.creator.purelib.iterdir()
|
||||
packages = sorted("==".join(i.stem.split("-")) for i in path if i.suffix == ".dist-info")
|
||||
lines.append(f" added seed packages: {', '.join(packages)}")
|
||||
|
||||
if self.session.activators:
|
||||
lines.append(f" activators {','.join(i.__class__.__name__ for i in self.session.activators)}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def run_with_catch(args: list[str] | None = None, env: MutableMapping[str, str] | None = None) -> None:
|
||||
from virtualenv.config.cli.parser import VirtualEnvOptions # noqa: PLC0415
|
||||
|
||||
env = os.environ if env is None else env
|
||||
options = VirtualEnvOptions()
|
||||
try:
|
||||
run(args, options, env)
|
||||
except (KeyboardInterrupt, SystemExit, Exception) as exception: # noqa: BLE001
|
||||
try:
|
||||
_exit_for_exception(options, exception)
|
||||
finally:
|
||||
for handler in LOGGER.handlers: # force flush of log messages before the trace is printed
|
||||
handler.flush()
|
||||
|
||||
|
||||
def _exit_for_exception(options: VirtualEnvOptions, exception: BaseException) -> None:
|
||||
if getattr(options, "with_traceback", False):
|
||||
raise exception
|
||||
if not (isinstance(exception, SystemExit) and exception.code == 0):
|
||||
LOGGER.error("%s: %s", type(exception).__name__, exception)
|
||||
sys.exit(exception.code if isinstance(exception, SystemExit) else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cov
|
||||
run_with_catch() # pragma: no cov
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .bash import BashActivator
|
||||
from .batch import BatchActivator
|
||||
from .cshell import CShellActivator
|
||||
from .fish import FishActivator
|
||||
from .nushell import NushellActivator
|
||||
from .powershell import PowerShellActivator
|
||||
from .python import PythonActivator
|
||||
from .xonsh import XonshActivator
|
||||
|
||||
__all__ = [
|
||||
"BashActivator",
|
||||
"BatchActivator",
|
||||
"CShellActivator",
|
||||
"FishActivator",
|
||||
"NushellActivator",
|
||||
"PowerShellActivator",
|
||||
"PythonActivator",
|
||||
"XonshActivator",
|
||||
]
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,61 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
from virtualenv.config.cli.parser import VirtualEnvOptions
|
||||
from virtualenv.create.creator import Creator
|
||||
|
||||
|
||||
class Activator(ABC):
|
||||
"""Generates activate script for the virtual environment."""
|
||||
|
||||
def __init__(self, options: VirtualEnvOptions) -> None:
|
||||
"""Create a new activator generator.
|
||||
|
||||
:param options: the parsed options as defined within :meth:`add_parser_arguments`
|
||||
|
||||
"""
|
||||
self.flag_prompt = os.path.basename(os.getcwd()) if options.prompt == "." else options.prompt
|
||||
|
||||
@classmethod
|
||||
def supports(cls, interpreter: PythonInfo) -> bool: # noqa: ARG003
|
||||
"""Check if the activation script is supported in the given interpreter.
|
||||
|
||||
:param interpreter: the interpreter we need to support
|
||||
|
||||
:returns: ``True`` if supported, ``False`` otherwise
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
@classmethod # noqa: B027
|
||||
def add_parser_arguments(cls, parser: ArgumentParser, interpreter: PythonInfo) -> None:
|
||||
"""Add CLI arguments for this activation script.
|
||||
|
||||
:param parser: the CLI parser
|
||||
:param interpreter: the interpreter this virtual environment is based of
|
||||
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def generate(self, creator: Creator) -> list[Path]:
|
||||
"""Generate activate script for the given creator.
|
||||
|
||||
:param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this virtual
|
||||
environment
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Activator",
|
||||
]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.activation.via_template import ViaTemplateActivator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from virtualenv.create.creator import Creator
|
||||
|
||||
|
||||
class BashActivator(ViaTemplateActivator):
|
||||
def templates(self) -> Iterator[str]:
|
||||
yield "activate.sh"
|
||||
|
||||
def as_name(self, template: str) -> str:
|
||||
return Path(template).stem
|
||||
|
||||
def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:
|
||||
data = super().replacements(creator, dest_folder)
|
||||
data.update({
|
||||
"__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
|
||||
"__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BashActivator",
|
||||
]
|
||||
BIN
Binary file not shown.
+130
@@ -0,0 +1,130 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
|
||||
if [ "${BASH_SOURCE-}" = "$0" ]; then
|
||||
echo "You must source this script: \$ source $0" >&2
|
||||
exit 33
|
||||
fi
|
||||
|
||||
deactivate () {
|
||||
unset -f pydoc >/dev/null 2>&1 || true
|
||||
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="$_OLD_VIRTUAL_PATH"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_TCL_LIBRARY:-}" ]; then
|
||||
TCL_LIBRARY="$_OLD_VIRTUAL_TCL_LIBRARY"
|
||||
export TCL_LIBRARY
|
||||
unset _OLD_VIRTUAL_TCL_LIBRARY
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_TK_LIBRARY:-}" ]; then
|
||||
TK_LIBRARY="$_OLD_VIRTUAL_TK_LIBRARY"
|
||||
export TK_LIBRARY
|
||||
unset _OLD_VIRTUAL_TK_LIBRARY
|
||||
fi
|
||||
if [ -n "${_OLD_PKG_CONFIG_PATH:-}" ]; then
|
||||
PKG_CONFIG_PATH="$_OLD_PKG_CONFIG_PATH"
|
||||
export PKG_CONFIG_PATH
|
||||
unset _OLD_PKG_CONFIG_PATH
|
||||
fi
|
||||
|
||||
# The hash command must be called to get it to forget past
|
||||
# commands. Without forgetting past commands the $PATH changes
|
||||
# we made may not be respected
|
||||
hash -r 2>/dev/null || true
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="$_OLD_VIRTUAL_PS1"
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
unset VIRTUAL_ENV_PROMPT
|
||||
if [ ! "${1-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
if [ ! -d __VIRTUAL_ENV__ ]; then
|
||||
echo "Virtual environment directory __VIRTUAL_ENV__ does not exist!" >&2
|
||||
CURRENT_PATH=$(realpath "${BASH_SOURCE[0]}")
|
||||
CURRENT_DIR=$(dirname "${CURRENT_PATH}")
|
||||
VIRTUAL_ENV="$(realpath "${CURRENT_DIR}/../")"
|
||||
else
|
||||
VIRTUAL_ENV=__VIRTUAL_ENV__
|
||||
fi
|
||||
|
||||
case "$(uname)" in
|
||||
CYGWIN*|MSYS*|MINGW*)
|
||||
VIRTUAL_ENV=$(cygpath "$VIRTUAL_ENV")
|
||||
;;
|
||||
esac
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/"__BIN_NAME__":$PATH"
|
||||
export PATH
|
||||
|
||||
_OLD_PKG_CONFIG_PATH="${PKG_CONFIG_PATH:-}"
|
||||
PKG_CONFIG_PATH="${VIRTUAL_ENV}/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
|
||||
export PKG_CONFIG_PATH
|
||||
|
||||
if [ "x"__VIRTUAL_PROMPT__ != x ] ; then
|
||||
VIRTUAL_ENV_PROMPT=__VIRTUAL_PROMPT__
|
||||
else
|
||||
VIRTUAL_ENV_PROMPT=$(basename "$VIRTUAL_ENV")
|
||||
fi
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ __TCL_LIBRARY__ != "" ]; then
|
||||
if [ -n "${TCL_LIBRARY:-}" ] ; then
|
||||
_OLD_VIRTUAL_TCL_LIBRARY="$TCL_LIBRARY"
|
||||
fi
|
||||
TCL_LIBRARY=__TCL_LIBRARY__
|
||||
export TCL_LIBRARY
|
||||
fi
|
||||
|
||||
if [ __TK_LIBRARY__ != "" ]; then
|
||||
if [ -n "${TK_LIBRARY:-}" ] ; then
|
||||
_OLD_VIRTUAL_TK_LIBRARY="$TK_LIBRARY"
|
||||
fi
|
||||
TK_LIBRARY=__TK_LIBRARY__
|
||||
export TK_LIBRARY
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1-}"
|
||||
PS1="(${VIRTUAL_ENV_PROMPT}) ${PS1-}"
|
||||
fi
|
||||
|
||||
# Make sure to unalias pydoc if it's already there
|
||||
alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
|
||||
|
||||
pydoc () {
|
||||
python -m pydoc "$@"
|
||||
}
|
||||
|
||||
# The hash command must be called to get it to forget past
|
||||
# commands. Without forgetting past commands the $PATH changes
|
||||
# we made may not be respected
|
||||
hash -r 2>/dev/null || true
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.activation.via_template import ViaTemplateActivator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
from virtualenv.create.creator import Creator
|
||||
|
||||
|
||||
class BatchActivator(ViaTemplateActivator):
|
||||
@classmethod
|
||||
def supports(cls, interpreter: PythonInfo) -> bool:
|
||||
return interpreter.os == "nt"
|
||||
|
||||
def templates(self) -> Iterator[str]:
|
||||
yield "activate.bat"
|
||||
yield "deactivate.bat"
|
||||
yield "pydoc.bat"
|
||||
|
||||
@staticmethod
|
||||
def quote(string: str) -> str:
|
||||
return string
|
||||
|
||||
def instantiate_template(self, replacements: dict[str, str], template: str, creator: Creator) -> str:
|
||||
# ensure the text has all newlines as \r\n - required by batch
|
||||
base = super().instantiate_template(replacements, template, creator)
|
||||
return base.replace(os.linesep, "\n").replace("\n", os.linesep)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BatchActivator",
|
||||
]
|
||||
BIN
Binary file not shown.
+59
@@ -0,0 +1,59 @@
|
||||
@REM This file is UTF-8 encoded, so we need to update the current code page while executing it
|
||||
@for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do @set _OLD_CODEPAGE=%%a
|
||||
|
||||
@if defined _OLD_CODEPAGE (
|
||||
"%SystemRoot%\System32\chcp.com" 65001 > nul
|
||||
)
|
||||
|
||||
@set "VIRTUAL_ENV=__VIRTUAL_ENV__"
|
||||
|
||||
@set "VIRTUAL_ENV_PROMPT=__VIRTUAL_PROMPT__"
|
||||
@if NOT DEFINED VIRTUAL_ENV_PROMPT (
|
||||
@for %%d in ("%VIRTUAL_ENV%") do @set "VIRTUAL_ENV_PROMPT=%%~nxd"
|
||||
)
|
||||
|
||||
@if defined _OLD_VIRTUAL_PROMPT (
|
||||
@set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
||||
) else (
|
||||
@if not defined PROMPT (
|
||||
@set "PROMPT=$P$G"
|
||||
)
|
||||
@if not defined VIRTUAL_ENV_DISABLE_PROMPT (
|
||||
@set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
|
||||
)
|
||||
)
|
||||
@if not defined VIRTUAL_ENV_DISABLE_PROMPT (
|
||||
@set "PROMPT=(%VIRTUAL_ENV_PROMPT%) %PROMPT%"
|
||||
)
|
||||
|
||||
@REM Don't use () to avoid problems with them in %PATH%
|
||||
@if defined _OLD_VIRTUAL_PYTHONHOME @goto ENDIFVHOME
|
||||
@set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
|
||||
:ENDIFVHOME
|
||||
|
||||
@set PYTHONHOME=
|
||||
|
||||
@if defined TCL_LIBRARY @set "_OLD_VIRTUAL_TCL_LIBRARY=%TCL_LIBRARY%"
|
||||
@if NOT "__TCL_LIBRARY__"=="" @set "TCL_LIBRARY=__TCL_LIBRARY__"
|
||||
|
||||
@if defined TK_LIBRARY @set "_OLD_VIRTUAL_TK_LIBRARY=%TK_LIBRARY%"
|
||||
@if NOT "__TK_LIBRARY__"=="" @set "TK_LIBRARY=__TK_LIBRARY__"
|
||||
|
||||
@if defined PKG_CONFIG_PATH @set "_OLD_PKG_CONFIG_PATH=%PKG_CONFIG_PATH%"
|
||||
@set "PKG_CONFIG_PATH=%VIRTUAL_ENV%\lib\pkgconfig;%PKG_CONFIG_PATH%"
|
||||
|
||||
@REM if defined _OLD_VIRTUAL_PATH (
|
||||
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH1
|
||||
@set "PATH=%_OLD_VIRTUAL_PATH%"
|
||||
:ENDIFVPATH1
|
||||
@REM ) else (
|
||||
@if defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH2
|
||||
@set "_OLD_VIRTUAL_PATH=%PATH%"
|
||||
:ENDIFVPATH2
|
||||
|
||||
@set "PATH=%VIRTUAL_ENV%\__BIN_NAME__;%PATH%"
|
||||
|
||||
@if defined _OLD_CODEPAGE (
|
||||
"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
|
||||
@set _OLD_CODEPAGE=
|
||||
)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
@set VIRTUAL_ENV=
|
||||
@set VIRTUAL_ENV_PROMPT=
|
||||
|
||||
@REM Don't use () to avoid problems with them in %PATH%
|
||||
@if not defined _OLD_VIRTUAL_PROMPT @goto ENDIFVPROMPT
|
||||
@set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
||||
@set _OLD_VIRTUAL_PROMPT=
|
||||
:ENDIFVPROMPT
|
||||
|
||||
@if not defined _OLD_VIRTUAL_PYTHONHOME @goto ENDIFVHOME
|
||||
@set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
|
||||
@set _OLD_VIRTUAL_PYTHONHOME=
|
||||
:ENDIFVHOME
|
||||
|
||||
@if defined _OLD_VIRTUAL_TCL_LIBRARY @set "TCL_LIBRARY=%_OLD_VIRTUAL_TCL_LIBRARY%"
|
||||
@if not defined _OLD_VIRTUAL_TCL_LIBRARY @set TCL_LIBRARY=
|
||||
@set _OLD_VIRTUAL_TCL_LIBRARY=
|
||||
|
||||
@if defined _OLD_VIRTUAL_TK_LIBRARY @set "TK_LIBRARY=%_OLD_VIRTUAL_TK_LIBRARY%"
|
||||
@if not defined _OLD_VIRTUAL_TK_LIBRARY @set TK_LIBRARY=
|
||||
@set _OLD_VIRTUAL_TK_LIBRARY=
|
||||
|
||||
@if defined _OLD_PKG_CONFIG_PATH @set "PKG_CONFIG_PATH=%_OLD_PKG_CONFIG_PATH%"
|
||||
@if not defined _OLD_PKG_CONFIG_PATH @set PKG_CONFIG_PATH=
|
||||
@set _OLD_PKG_CONFIG_PATH=
|
||||
|
||||
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH
|
||||
@set "PATH=%_OLD_VIRTUAL_PATH%"
|
||||
@set _OLD_VIRTUAL_PATH=
|
||||
:ENDIFVPATH
|
||||
@@ -0,0 +1 @@
|
||||
"python.exe" -m pydoc %*
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.activation.via_template import ViaTemplateActivator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
|
||||
class CShellActivator(ViaTemplateActivator):
|
||||
@classmethod
|
||||
def supports(cls, interpreter: PythonInfo) -> bool:
|
||||
return interpreter.os != "nt"
|
||||
|
||||
def templates(self) -> Iterator[str]:
|
||||
yield "activate.csh"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CShellActivator",
|
||||
]
|
||||
BIN
Binary file not shown.
+74
@@ -0,0 +1,74 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
|
||||
set newline='\
|
||||
'
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_TCL_LIBRARY != 0 && setenv TCL_LIBRARY "$_OLD_VIRTUAL_TCL_LIBRARY:q" && unset _OLD_VIRTUAL_TCL_LIBRARY || unsetenv TCL_LIBRARY; test $?_OLD_VIRTUAL_TK_LIBRARY != 0 && setenv TK_LIBRARY "$_OLD_VIRTUAL_TK_LIBRARY:q" && unset _OLD_VIRTUAL_TK_LIBRARY || unsetenv TK_LIBRARY; test $?_OLD_PKG_CONFIG_PATH != 0 && setenv PKG_CONFIG_PATH "$_OLD_PKG_CONFIG_PATH:q" && unset _OLD_PKG_CONFIG_PATH || unsetenv PKG_CONFIG_PATH; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV __VIRTUAL_ENV__
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH:q"
|
||||
setenv PATH "$VIRTUAL_ENV:q/"__BIN_NAME__":$PATH:q"
|
||||
|
||||
if ($?PKG_CONFIG_PATH) then
|
||||
set _OLD_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
|
||||
setenv PKG_CONFIG_PATH "${VIRTUAL_ENV}/lib/pkgconfig:${PKG_CONFIG_PATH}"
|
||||
else
|
||||
setenv PKG_CONFIG_PATH "${VIRTUAL_ENV}/lib/pkgconfig"
|
||||
endif
|
||||
|
||||
if (__TCL_LIBRARY__ != "") then
|
||||
if ($?TCL_LIBRARY) then
|
||||
set _OLD_VIRTUAL_TCL_LIBRARY="$TCL_LIBRARY"
|
||||
endif
|
||||
setenv TCL_LIBRARY __TCL_LIBRARY__
|
||||
endif
|
||||
|
||||
if (__TK_LIBRARY__ != "") then
|
||||
if ($?TK_LIBRARY) then
|
||||
set _OLD_VIRTUAL_TK_LIBRARY="$TK_LIBRARY"
|
||||
endif
|
||||
setenv TK_LIBRARY __TK_LIBRARY__
|
||||
endif
|
||||
|
||||
if (__VIRTUAL_PROMPT__ != "") then
|
||||
setenv VIRTUAL_ENV_PROMPT __VIRTUAL_PROMPT__
|
||||
else
|
||||
setenv VIRTUAL_ENV_PROMPT "$VIRTUAL_ENV:t:q"
|
||||
endif
|
||||
|
||||
if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then
|
||||
if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then
|
||||
set do_prompt = "1"
|
||||
else
|
||||
set do_prompt = "0"
|
||||
endif
|
||||
else
|
||||
set do_prompt = "1"
|
||||
endif
|
||||
|
||||
if ( $do_prompt == "1" ) then
|
||||
# Could be in a non-interactive environment,
|
||||
# in which case, $prompt is undefined and we wouldn't
|
||||
# care about the prompt anyway.
|
||||
if ( $?prompt ) then
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt:q"
|
||||
if ( "$prompt:q" =~ *"$newline:q"* ) then
|
||||
:
|
||||
else
|
||||
set prompt = '('"$VIRTUAL_ENV_PROMPT:q"') '"$prompt:q"
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
unset env_name
|
||||
unset do_prompt
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.activation.via_template import ViaTemplateActivator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
from virtualenv.create.creator import Creator
|
||||
|
||||
|
||||
class FishActivator(ViaTemplateActivator):
|
||||
def templates(self) -> Iterator[str]:
|
||||
yield "activate.fish"
|
||||
|
||||
def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:
|
||||
data = super().replacements(creator, dest_folder)
|
||||
data.update({
|
||||
"__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
|
||||
"__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
__all__ = [
|
||||
"FishActivator",
|
||||
]
|
||||
BIN
Binary file not shown.
+124
@@ -0,0 +1,124 @@
|
||||
# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*.
|
||||
# Do not run it directly.
|
||||
|
||||
function deactivate -d 'Exit virtualenv mode and return to the normal environment.'
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
|
||||
if test -n __TCL_LIBRARY__
|
||||
if test -n "$_OLD_VIRTUAL_TCL_LIBRARY";
|
||||
set -gx TCL_LIBRARY "$_OLD_VIRTUAL_TCL_LIBRARY";
|
||||
set -e _OLD_VIRTUAL_TCL_LIBRARY;
|
||||
else;
|
||||
set -e TCL_LIBRARY;
|
||||
end
|
||||
end
|
||||
if test -n __TK_LIBRARY__
|
||||
if test -n "$_OLD_VIRTUAL_TK_LIBRARY";
|
||||
set -gx TK_LIBRARY "$_OLD_VIRTUAL_TK_LIBRARY";
|
||||
set -e _OLD_VIRTUAL_TK_LIBRARY;
|
||||
else;
|
||||
set -e TK_LIBRARY;
|
||||
end
|
||||
end
|
||||
|
||||
if test -n "$_OLD_PKG_CONFIG_PATH"
|
||||
set -gx PKG_CONFIG_PATH "$_OLD_PKG_CONFIG_PATH"
|
||||
set -e _OLD_PKG_CONFIG_PATH
|
||||
end
|
||||
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
and builtin functions -q _old_fish_prompt
|
||||
# Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`.
|
||||
set -l fish_function_path
|
||||
|
||||
# Erase virtualenv's `fish_prompt` and restore the original.
|
||||
builtin functions -e fish_prompt
|
||||
builtin functions -c _old_fish_prompt fish_prompt
|
||||
builtin functions -e _old_fish_prompt
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
set -e VIRTUAL_ENV_PROMPT
|
||||
|
||||
if test "$argv[1]" != 'nondestructive'
|
||||
# Self-destruct!
|
||||
builtin functions -e pydoc
|
||||
builtin functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV __VIRTUAL_ENV__
|
||||
if string match -qr 'CYGWIN|MSYS|MINGW' (uname)
|
||||
set -gx VIRTUAL_ENV (cygpath -u $VIRTUAL_ENV)
|
||||
end
|
||||
|
||||
set -gx _OLD_PKG_CONFIG_PATH "$PKG_CONFIG_PATH"
|
||||
set -gx PKG_CONFIG_PATH "$VIRTUAL_ENV/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV"'/'__BIN_NAME__ $PATH
|
||||
|
||||
if test -n __TCL_LIBRARY__
|
||||
if set -q TCL_LIBRARY;
|
||||
set -gx _OLD_VIRTUAL_TCL_LIBRARY $TCL_LIBRARY;
|
||||
end
|
||||
set -gx TCL_LIBRARY '__TCL_LIBRARY__'
|
||||
end
|
||||
if test -n __TK_LIBRARY__
|
||||
if set -q TK_LIBRARY;
|
||||
set -gx _OLD_VIRTUAL_TK_LIBRARY $TK_LIBRARY;
|
||||
end
|
||||
set -gx TK_LIBRARY '__TK_LIBRARY__'
|
||||
end
|
||||
|
||||
# Prompt override provided?
|
||||
# If not, just use the environment name.
|
||||
if test -n __VIRTUAL_PROMPT__
|
||||
set -gx VIRTUAL_ENV_PROMPT __VIRTUAL_PROMPT__
|
||||
else
|
||||
set -gx VIRTUAL_ENV_PROMPT (basename "$VIRTUAL_ENV")
|
||||
end
|
||||
|
||||
# Unset `$PYTHONHOME` if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
function pydoc
|
||||
python -m pydoc $argv
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# Copy the current `fish_prompt` function as `_old_fish_prompt`.
|
||||
builtin functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# Route every builtin the prompt path uses through `builtin` so a user function that shadows
|
||||
# `printf`, `string`, `echo`, or `source`/`.` cannot hijack the prompt (Issue #93858 follow-up,
|
||||
# CPython gh-140006). A dot-style directory navigator redefining `.` is the common trigger.
|
||||
function fish_prompt
|
||||
set -l old_status $status
|
||||
# Run the user's prompt first; it might depend on (pipe)status.
|
||||
set -l prompt (_old_fish_prompt)
|
||||
|
||||
builtin printf '(%s) ' $VIRTUAL_ENV_PROMPT
|
||||
|
||||
builtin string join -- \n $prompt # handle multi-line prompts
|
||||
builtin echo "exit $old_status" | builtin source -
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
end
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.activation.via_template import ViaTemplateActivator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
from virtualenv.create.creator import Creator
|
||||
|
||||
|
||||
class NushellActivator(ViaTemplateActivator):
|
||||
def templates(self) -> Iterator[str]:
|
||||
yield "activate.nu"
|
||||
|
||||
@staticmethod
|
||||
def quote(string: str) -> str:
|
||||
"""Nushell supports raw strings like: r###'this is a string'###.
|
||||
|
||||
https://github.com/nushell/nushell.github.io/blob/main/book/working_with_strings.md
|
||||
|
||||
This method finds the maximum continuous sharps in the string and then quote it with an extra sharp.
|
||||
|
||||
"""
|
||||
max_sharps = 0
|
||||
current_sharps = 0
|
||||
for char in string:
|
||||
if char == "#":
|
||||
current_sharps += 1
|
||||
max_sharps = max(current_sharps, max_sharps)
|
||||
else:
|
||||
current_sharps = 0
|
||||
wrapping = "#" * (max_sharps + 1)
|
||||
return f"r{wrapping}'{string}'{wrapping}"
|
||||
|
||||
def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]: # noqa: ARG002
|
||||
return {
|
||||
"__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
|
||||
"__VIRTUAL_ENV__": str(creator.dest),
|
||||
"__VIRTUAL_NAME__": creator.env_name,
|
||||
"__BIN_NAME__": str(creator.bin_dir.relative_to(creator.dest)),
|
||||
"__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
|
||||
"__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
|
||||
}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"NushellActivator",
|
||||
]
|
||||
BIN
Binary file not shown.
+91
@@ -0,0 +1,91 @@
|
||||
# virtualenv activation module:
|
||||
# - Activate with `overlay use activate.nu`
|
||||
# - Deactivate with `deactivate`, as usual
|
||||
#
|
||||
# To customize the overlay name, call `overlay use activate.nu as foo`, but then `deactivate` won't work —
|
||||
# you'd need to run `overlay hide foo` manually. Do not activate with `use activate.nu *`; that does not
|
||||
# create an overlay and deactivation will fail.
|
||||
|
||||
module warning {
|
||||
export-env {
|
||||
const file = path self
|
||||
error make -u {
|
||||
msg: $"`($file | path basename)` is meant to be used with `overlay use`, not `source`"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
use warning
|
||||
|
||||
export-env {
|
||||
|
||||
let nu_ver = (version | get version | split row '.' | take 2 | each { into int })
|
||||
if $nu_ver.0 == 0 and $nu_ver.1 < 106 {
|
||||
error make {
|
||||
msg: 'virtualenv Nushell activation requires Nushell 0.106 or greater.'
|
||||
}
|
||||
}
|
||||
|
||||
def is-string [x] {
|
||||
($x | describe) == 'string'
|
||||
}
|
||||
|
||||
def has-env [...names] {
|
||||
$names | each {|n| $n in $env } | all {|i| $i }
|
||||
}
|
||||
|
||||
def is-env-true [name: string] {
|
||||
if (has-env $name) {
|
||||
let val = ($env | get --optional $name)
|
||||
if ($val | describe) == 'bool' {
|
||||
$val
|
||||
} else {
|
||||
not ($val | is-empty)
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let virtual_env = __VIRTUAL_ENV__
|
||||
let bin = __BIN_NAME__
|
||||
let path_name = if (has-env 'Path') { 'Path' } else { 'PATH' }
|
||||
let venv_path = ([$virtual_env $bin] | path join)
|
||||
let new_path = ($env | get $path_name | prepend $venv_path)
|
||||
let virtual_env_prompt = if (__VIRTUAL_PROMPT__ | is-empty) {
|
||||
($virtual_env | path basename)
|
||||
} else {
|
||||
__VIRTUAL_PROMPT__
|
||||
}
|
||||
let old_pkg_config_path = if (has-env 'PKG_CONFIG_PATH') { $env.PKG_CONFIG_PATH } else { '' }
|
||||
let new_pkg_config_path = $'($virtual_env)/lib/pkgconfig:($old_pkg_config_path)'
|
||||
let new_env = { $path_name: $new_path VIRTUAL_ENV: $virtual_env VIRTUAL_ENV_PROMPT: $virtual_env_prompt PKG_CONFIG_PATH: $new_pkg_config_path }
|
||||
if (has-env 'TCL_LIBRARY') {
|
||||
let $new_env = $new_env | insert TCL_LIBRARY __TCL_LIBRARY__
|
||||
}
|
||||
if (has-env 'TK_LIBRARY') {
|
||||
let $new_env = $new_env | insert TK_LIBRARY __TK_LIBRARY__
|
||||
}
|
||||
let old_prompt_command = if (has-env 'PROMPT_COMMAND') { $env.PROMPT_COMMAND } else { '' }
|
||||
let new_env = if (is-env-true 'VIRTUAL_ENV_DISABLE_PROMPT') {
|
||||
$new_env
|
||||
} else {
|
||||
let virtual_prefix = $'(char lparen)($virtual_env_prompt)(char rparen) '
|
||||
let new_prompt = if (has-env 'PROMPT_COMMAND') {
|
||||
if ('closure' in ($old_prompt_command | describe)) {
|
||||
{|| $'($virtual_prefix)(do $old_prompt_command)' }
|
||||
} else {
|
||||
{|| $'($virtual_prefix)($old_prompt_command)' }
|
||||
}
|
||||
} else {
|
||||
{|| $'($virtual_prefix)' }
|
||||
}
|
||||
$new_env | merge { PROMPT_COMMAND: $new_prompt VIRTUAL_PREFIX: $virtual_prefix }
|
||||
}
|
||||
load-env $new_env
|
||||
}
|
||||
|
||||
export alias pydoc = python -m pydoc
|
||||
# If deactivate errors "not an active overlay": activate with `overlay use activate.nu` not `use activate.nu *`; for custom names use `overlay hide NAME`
|
||||
export alias deactivate = overlay hide activate
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.activation.via_template import ViaTemplateActivator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
|
||||
class PowerShellActivator(ViaTemplateActivator):
|
||||
def templates(self) -> Iterator[str]:
|
||||
yield "activate.ps1"
|
||||
|
||||
@staticmethod
|
||||
def quote(string: str) -> str:
|
||||
"""This should satisfy PowerShell quoting rules [1], unless the quoted string is passed directly to Windows native commands [2].
|
||||
|
||||
[1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
|
||||
[2]:
|
||||
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters
|
||||
|
||||
"""
|
||||
string = string.replace("'", "''")
|
||||
return f"'{string}'"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PowerShellActivator",
|
||||
]
|
||||
BIN
Binary file not shown.
+213
@@ -0,0 +1,213 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
activate.ps1
|
||||
Activates the Python virtual environment that contains the activate.ps1 script.
|
||||
|
||||
.Example
|
||||
activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
Write-Verbose "File exists, parse ``key = value`` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
function global:deactivate([switch] $NonDestructive) {
|
||||
if (Test-Path variable:_OLD_VIRTUAL_PATH) {
|
||||
$env:PATH = $variable:_OLD_VIRTUAL_PATH
|
||||
Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global
|
||||
}
|
||||
|
||||
if (Test-Path variable:_OLD_VIRTUAL_TCL_LIBRARY) {
|
||||
$env:TCL_LIBRARY = $variable:_OLD_VIRTUAL_TCL_LIBRARY
|
||||
Remove-Variable "_OLD_VIRTUAL_TCL_LIBRARY" -Scope global
|
||||
} else {
|
||||
if (Test-Path env:TCL_LIBRARY) {
|
||||
Remove-Item env:TCL_LIBRARY -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path variable:_OLD_VIRTUAL_TK_LIBRARY) {
|
||||
$env:TK_LIBRARY = $variable:_OLD_VIRTUAL_TK_LIBRARY
|
||||
Remove-Variable "_OLD_VIRTUAL_TK_LIBRARY" -Scope global
|
||||
} else {
|
||||
if (Test-Path env:TK_LIBRARY) {
|
||||
Remove-Item env:TK_LIBRARY -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path variable:_OLD_PKG_CONFIG_PATH) {
|
||||
$env:PKG_CONFIG_PATH = $variable:_OLD_PKG_CONFIG_PATH
|
||||
Remove-Variable "_OLD_PKG_CONFIG_PATH" -Scope global
|
||||
}
|
||||
|
||||
if (Test-Path function:_old_virtual_prompt) {
|
||||
$function:prompt = $function:_old_virtual_prompt
|
||||
Remove-Item function:\_old_virtual_prompt
|
||||
}
|
||||
|
||||
if ($env:VIRTUAL_ENV) {
|
||||
Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if ($env:VIRTUAL_ENV_PROMPT) {
|
||||
Remove-Item env:VIRTUAL_ENV_PROMPT -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
if (!$NonDestructive) {
|
||||
Remove-Item function:deactivate
|
||||
Remove-Item function:pydoc
|
||||
}
|
||||
}
|
||||
|
||||
function global:pydoc {
|
||||
& python -m pydoc $args
|
||||
}
|
||||
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
} else {
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
} else {
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose "Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt']
|
||||
} elseif (__VIRTUAL_PROMPT__ -ne "") {
|
||||
$Prompt = __VIRTUAL_PROMPT__
|
||||
} else {
|
||||
$Prompt = Split-Path -Path $VenvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
deactivate -nondestructive
|
||||
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||
|
||||
if (__TCL_LIBRARY__ -ne "") {
|
||||
if (Test-Path env:TCL_LIBRARY) {
|
||||
New-Variable -Scope global -Name _OLD_VIRTUAL_TCL_LIBRARY -Value $env:TCL_LIBRARY
|
||||
}
|
||||
$env:TCL_LIBRARY = __TCL_LIBRARY__
|
||||
}
|
||||
|
||||
if (__TK_LIBRARY__ -ne "") {
|
||||
if (Test-Path env:TK_LIBRARY) {
|
||||
New-Variable -Scope global -Name _OLD_VIRTUAL_TK_LIBRARY -Value $env:TK_LIBRARY
|
||||
}
|
||||
$env:TK_LIBRARY = __TK_LIBRARY__
|
||||
}
|
||||
|
||||
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
|
||||
|
||||
if (Test-Path env:PKG_CONFIG_PATH) {
|
||||
New-Variable -Scope global -Name _OLD_PKG_CONFIG_PATH -Value $env:PKG_CONFIG_PATH
|
||||
}
|
||||
$env:PKG_CONFIG_PATH = "$env:VIRTUAL_ENV\lib\pkgconfig;$env:PKG_CONFIG_PATH"
|
||||
|
||||
$env:PATH = "$env:VIRTUAL_ENV/" + __BIN_NAME__ + __PATH_SEP__ + $env:PATH
|
||||
|
||||
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
function global:_old_virtual_prompt {
|
||||
""
|
||||
}
|
||||
$function:_old_virtual_prompt = $function:prompt
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($($_PYTHON_VENV_PROMPT_PREFIX)) "
|
||||
_old_virtual_prompt
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.activation.via_template import ViaTemplateActivator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
from virtualenv.create.creator import Creator
|
||||
|
||||
|
||||
class PythonActivator(ViaTemplateActivator):
|
||||
def templates(self) -> Iterator[str]:
|
||||
yield "activate_this.py"
|
||||
|
||||
@staticmethod
|
||||
def quote(string: str) -> str:
|
||||
return repr(string)
|
||||
|
||||
def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:
|
||||
replacements = super().replacements(creator, dest_folder)
|
||||
lib_folders = OrderedDict((os.path.relpath(str(i), str(dest_folder)), None) for i in creator.libs)
|
||||
lib_folders = os.pathsep.join(lib_folders.keys())
|
||||
replacements.update(
|
||||
{
|
||||
"__LIB_FOLDERS__": lib_folders,
|
||||
"__DECODE_PATH__": "",
|
||||
},
|
||||
)
|
||||
return replacements
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PythonActivator",
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+45
@@ -0,0 +1,45 @@
|
||||
"""Activate virtualenv for current interpreter:
|
||||
|
||||
import runpy runpy.run_path(this_file)
|
||||
|
||||
This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
|
||||
|
||||
""" # noqa: D415
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import site
|
||||
import sys
|
||||
|
||||
try:
|
||||
abs_file = os.path.abspath(__file__)
|
||||
except NameError as exc:
|
||||
msg = "You must use import runpy; runpy.run_path(this_file)"
|
||||
raise AssertionError(msg) from exc
|
||||
|
||||
bin_dir = os.path.dirname(abs_file)
|
||||
base = bin_dir[: -len(__BIN_NAME__) - 1] # ty: ignore[unresolved-reference]
|
||||
|
||||
# prepend bin to PATH (this file is inside the bin directory)
|
||||
os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
|
||||
os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
|
||||
os.environ["VIRTUAL_ENV_PROMPT"] = __VIRTUAL_PROMPT__ or os.path.basename(base) # ty: ignore[unresolved-reference]
|
||||
|
||||
# Set PKG_CONFIG_PATH to include the virtualenv's pkgconfig directory
|
||||
pkg_config_path = os.path.join(base, "lib", "pkgconfig")
|
||||
existing_pkg_config_path = os.environ.get("PKG_CONFIG_PATH", "")
|
||||
if existing_pkg_config_path:
|
||||
os.environ["PKG_CONFIG_PATH"] = os.pathsep.join([pkg_config_path, existing_pkg_config_path])
|
||||
else:
|
||||
os.environ["PKG_CONFIG_PATH"] = pkg_config_path
|
||||
|
||||
# add the virtual environments libraries to the host python import mechanism
|
||||
prev_length = len(sys.path)
|
||||
for lib in __LIB_FOLDERS__.split(os.pathsep): # ty: ignore[unresolved-reference]
|
||||
path = os.path.realpath(os.path.join(bin_dir, lib))
|
||||
site.addsitedir(path.decode("utf-8") if __DECODE_PATH__ else path) # ty: ignore[unresolved-reference,unresolved-attribute]
|
||||
sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
|
||||
|
||||
sys.real_prefix = sys.prefix # ty: ignore[unresolved-attribute]
|
||||
sys.prefix = base
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .activator import Activator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Iterator
|
||||
from pathlib import Path
|
||||
|
||||
from virtualenv.create.creator import Creator
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from importlib.resources import files
|
||||
|
||||
def read_binary(module_name: str, filename: str) -> bytes:
|
||||
return (files(module_name) / filename).read_bytes()
|
||||
|
||||
else:
|
||||
from importlib.resources import read_binary
|
||||
|
||||
|
||||
class ViaTemplateActivator(Activator, ABC):
|
||||
@abstractmethod
|
||||
def templates(self) -> Iterator[str]:
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def quote(string: str) -> str:
|
||||
"""Quote strings in the activation script.
|
||||
|
||||
:param string: the string to quote
|
||||
|
||||
:returns: quoted string that works in the activation script
|
||||
|
||||
"""
|
||||
return shlex.quote(string)
|
||||
|
||||
def generate(self, creator: Creator) -> list[Path]:
|
||||
dest_folder = creator.bin_dir
|
||||
replacements = self.replacements(creator, dest_folder)
|
||||
generated = self._generate(replacements, self.templates(), dest_folder, creator)
|
||||
if self.flag_prompt is not None:
|
||||
creator.pyenv_cfg["prompt"] = self.flag_prompt
|
||||
return generated
|
||||
|
||||
def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]: # noqa: ARG002
|
||||
return {
|
||||
"__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
|
||||
"__VIRTUAL_ENV__": str(creator.dest),
|
||||
"__VIRTUAL_NAME__": creator.env_name,
|
||||
"__BIN_NAME__": str(creator.bin_dir.relative_to(creator.dest)),
|
||||
"__PATH_SEP__": os.pathsep,
|
||||
"__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
|
||||
"__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
|
||||
}
|
||||
|
||||
def _generate(
|
||||
self, replacements: dict[str, str], templates: Iterable[str], to_folder: Path, creator: Creator
|
||||
) -> list[Path]:
|
||||
generated = []
|
||||
for template in templates:
|
||||
text = self.instantiate_template(replacements, template, creator)
|
||||
dest = to_folder / self.as_name(template)
|
||||
# remove the file if it already exists - this prevents permission
|
||||
# errors when the dest is not writable
|
||||
if dest.exists():
|
||||
dest.unlink()
|
||||
# Powershell assumes Windows 1252 encoding when reading files without BOM
|
||||
encoding = "utf-8-sig" if str(template).endswith(".ps1") else "utf-8"
|
||||
# use write_bytes to avoid platform specific line normalization (\n -> \r\n)
|
||||
dest.write_bytes(text.encode(encoding))
|
||||
generated.append(dest)
|
||||
return generated
|
||||
|
||||
def as_name(self, template: str) -> str:
|
||||
return template
|
||||
|
||||
def instantiate_template(self, replacements: dict[str, str], template: str, creator: Creator) -> str:
|
||||
# read content as binary to avoid platform specific line normalization (\n -> \r\n)
|
||||
binary = read_binary(self.__module__, template)
|
||||
text = binary.decode("utf-8", errors="strict")
|
||||
for key, value in replacements.items():
|
||||
value_uni = self._repr_unicode(creator, value)
|
||||
text = text.replace(key, self.quote(value_uni))
|
||||
return text
|
||||
|
||||
@staticmethod
|
||||
def _repr_unicode(creator: Creator, value: str) -> str: # noqa: ARG004
|
||||
return value # by default, we just let it be unicode
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ViaTemplateActivator",
|
||||
]
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.activation.via_template import ViaTemplateActivator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
from virtualenv.create.creator import Creator
|
||||
|
||||
|
||||
class XonshActivator(ViaTemplateActivator):
|
||||
def templates(self) -> Iterator[str]:
|
||||
yield "activate.xsh"
|
||||
|
||||
@staticmethod
|
||||
def quote(string: str) -> str:
|
||||
"""Quote as a Python literal — xonsh parses the activation script as Python."""
|
||||
return repr(string)
|
||||
|
||||
def replacements(self, creator: Creator, dest_folder: Path) -> dict[str, str]:
|
||||
data = super().replacements(creator, dest_folder)
|
||||
data.update({
|
||||
"__TCL_LIBRARY__": getattr(creator.interpreter, "tcl_lib", None) or "",
|
||||
"__TK_LIBRARY__": getattr(creator.interpreter, "tk_lib", None) or "",
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
__all__ = [
|
||||
"XonshActivator",
|
||||
]
|
||||
BIN
Binary file not shown.
+101
@@ -0,0 +1,101 @@
|
||||
# This file must be used with "source bin/activate.xsh" *from xonsh*.
|
||||
# You cannot run it directly.
|
||||
|
||||
|
||||
class _VirtualEnvActivator:
|
||||
"""xonsh activation for virtualenv.
|
||||
|
||||
Rendered into ``<venv>/bin/activate.xsh`` by the template in
|
||||
``src/virtualenv/activation/xonsh/`` at generate-time.
|
||||
|
||||
- Project: https://github.com/pypa/virtualenv
|
||||
- Template source: https://github.com/pypa/virtualenv/tree/main/src/virtualenv/activation/xonsh
|
||||
"""
|
||||
|
||||
# Stashed in _OLD_VIRTUAL_{name} when the variable was not set before
|
||||
# activation. deactivate treats this as "unset" rather than "restore to
|
||||
# this string". Chosen to be vanishingly unlikely as a real value.
|
||||
_UNSET_SENTINEL = "__virtualenv_was_not_set__"
|
||||
|
||||
def __init__(self):
|
||||
self.env = __xonsh__.env
|
||||
try:
|
||||
# Values substituted by virtualenv's XonshActivator at generate-time.
|
||||
# If this file was sourced before template rendering, the bare
|
||||
# identifiers below resolve to NameError.
|
||||
self.embedded_virtual_env = __VIRTUAL_ENV__
|
||||
self.embedded_virtual_prompt = __VIRTUAL_PROMPT__
|
||||
self.embedded_bin_name = __BIN_NAME__
|
||||
self.embedded_tcl_library = __TCL_LIBRARY__
|
||||
self.embedded_tk_library = __TK_LIBRARY__
|
||||
except NameError:
|
||||
print("virtualenv activate.xsh: template was not rendered")
|
||||
raise
|
||||
# Variables this activator may save+override; deactivate walks this list.
|
||||
self.managed_vars = ("PATH", "PYTHONHOME", "TCL_LIBRARY", "TK_LIBRARY")
|
||||
|
||||
def _backup_name(self, name):
|
||||
return f"_OLD_VIRTUAL_{name}"
|
||||
|
||||
def _save(self, name):
|
||||
"""Stash the current value (or sentinel if unset) so deactivate can undo."""
|
||||
backup = self._backup_name(name)
|
||||
self.env[backup] = self.env[name] if name in self.env else self._UNSET_SENTINEL
|
||||
|
||||
def _override(self, name, value):
|
||||
self._save(name)
|
||||
self.env[name] = value
|
||||
|
||||
def _drop(self, name):
|
||||
self._save(name)
|
||||
self.env.pop(name, None)
|
||||
|
||||
def register_pydoc(self):
|
||||
aliases["pydoc"] = ["python", "-m", "pydoc"]
|
||||
|
||||
def unregister_pydoc(self):
|
||||
aliases.pop("pydoc", None)
|
||||
|
||||
def activate(self):
|
||||
from os.path import join, basename
|
||||
|
||||
aliases["deactivate"] = self.deactivate
|
||||
self.deactivate(["nondestructive"]) # wipe any stale state from a prior activation
|
||||
|
||||
$VIRTUAL_ENV = self.embedded_virtual_env
|
||||
$VIRTUAL_ENV_PROMPT = self.embedded_virtual_prompt or basename($VIRTUAL_ENV)
|
||||
|
||||
self._override("PATH", [join($VIRTUAL_ENV, self.embedded_bin_name), *$PATH])
|
||||
self._drop("PYTHONHOME")
|
||||
for name, value in (("TCL_LIBRARY", self.embedded_tcl_library), ("TK_LIBRARY", self.embedded_tk_library)):
|
||||
if value:
|
||||
self._override(name, value)
|
||||
|
||||
self.register_pydoc()
|
||||
|
||||
def deactivate(self, args=None):
|
||||
for name in self.managed_vars:
|
||||
backup = self._backup_name(name)
|
||||
if backup not in self.env:
|
||||
continue
|
||||
previous = self.env[backup]
|
||||
del self.env[backup]
|
||||
if previous == self._UNSET_SENTINEL:
|
||||
self.env.pop(name, None)
|
||||
else:
|
||||
self.env[name] = previous
|
||||
for name in ("VIRTUAL_ENV", "VIRTUAL_ENV_PROMPT"):
|
||||
self.env.pop(name, None)
|
||||
self.unregister_pydoc()
|
||||
if args is None or "nondestructive" not in args:
|
||||
del aliases["deactivate"]
|
||||
try:
|
||||
del __xonsh__.xontrib.virtualenv
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
if not hasattr(__xonsh__, "xontrib"):
|
||||
__xonsh__.xontrib = __xonsh__.imp.types.SimpleNamespace()
|
||||
__xonsh__.xontrib.virtualenv = _VirtualEnvActivator()
|
||||
__xonsh__.xontrib.virtualenv.activate()
|
||||
@@ -0,0 +1,81 @@
|
||||
"""Application data stored by virtualenv."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from platformdirs import user_cache_dir, user_data_dir
|
||||
|
||||
from .na import AppDataDisabled
|
||||
from .read_only import ReadOnlyAppData
|
||||
from .via_disk_folder import AppDataDiskFolder
|
||||
from .via_tempdir import TempAppData
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
|
||||
from .base import AppData
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _default_app_data_dir(env: Mapping[str, str]) -> str:
|
||||
key = "VIRTUALENV_OVERRIDE_APP_DATA"
|
||||
if key in env:
|
||||
return env[key]
|
||||
return _cache_dir_with_migration()
|
||||
|
||||
|
||||
def _cache_dir_with_migration() -> str:
|
||||
new_dir = user_cache_dir(appname="virtualenv", appauthor="pypa")
|
||||
old_dir = user_data_dir(appname="virtualenv", appauthor="pypa")
|
||||
if new_dir == old_dir:
|
||||
return new_dir
|
||||
if os.path.isdir(old_dir) and not os.path.isdir(new_dir):
|
||||
LOGGER.info("migrating app data from %s to %s", old_dir, new_dir)
|
||||
try:
|
||||
shutil.move(old_dir, new_dir)
|
||||
except OSError as exception:
|
||||
LOGGER.warning(
|
||||
"could not migrate app data from %s to %s: %r, using old location", old_dir, new_dir, exception
|
||||
)
|
||||
return old_dir
|
||||
return new_dir
|
||||
|
||||
|
||||
def make_app_data(folder: str | None, **kwargs: Any) -> AppData: # noqa: ANN401
|
||||
is_read_only = kwargs.pop("read_only")
|
||||
env = kwargs.pop("env")
|
||||
if kwargs: # py3+ kwonly
|
||||
msg = "unexpected keywords: {}"
|
||||
raise TypeError(msg)
|
||||
|
||||
if folder is None:
|
||||
folder = _default_app_data_dir(env)
|
||||
folder = os.path.abspath(folder)
|
||||
|
||||
if is_read_only:
|
||||
return ReadOnlyAppData(folder)
|
||||
|
||||
try:
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
LOGGER.debug("created app data folder %s", folder)
|
||||
except OSError as exception:
|
||||
LOGGER.info("could not create app data folder %s due to %r", folder, exception)
|
||||
|
||||
if os.access(folder, os.W_OK):
|
||||
return AppDataDiskFolder(folder)
|
||||
LOGGER.debug("app data folder %s has no write access", folder)
|
||||
return TempAppData()
|
||||
|
||||
|
||||
__all__ = (
|
||||
"AppDataDisabled",
|
||||
"AppDataDiskFolder",
|
||||
"ReadOnlyAppData",
|
||||
"TempAppData",
|
||||
"make_app_data",
|
||||
)
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,167 @@
|
||||
"""Application data stored by virtualenv."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.info import IS_ZIPAPP
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
class AppData(ABC):
|
||||
"""Abstract storage interface for the virtualenv application."""
|
||||
|
||||
@abstractmethod
|
||||
def close(self) -> None:
|
||||
"""Called before virtualenv exits."""
|
||||
|
||||
@abstractmethod
|
||||
def reset(self) -> None:
|
||||
"""Called when the user passes in the reset app data."""
|
||||
|
||||
@abstractmethod
|
||||
def py_info(self, path: Path) -> ContentStore:
|
||||
"""Return a content store for cached interpreter information at the given path.
|
||||
|
||||
:param path: the interpreter executable path
|
||||
|
||||
:returns: a content store for the cached data
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def py_info_clear(self) -> None:
|
||||
"""Clear all cached interpreter information."""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def can_update(self) -> bool:
|
||||
"""``True`` if this app data store supports updating cached content."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def embed_update_log(self, distribution: str, for_py_version: str) -> ContentStore:
|
||||
"""Return a content store for the embed update log of a distribution.
|
||||
|
||||
:param distribution: the package name (e.g. ``pip``)
|
||||
:param for_py_version: the target Python version string
|
||||
|
||||
:returns: a content store for the update log
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def house(self) -> Path:
|
||||
"""The root directory of the application data store."""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def transient(self) -> bool:
|
||||
"""``True`` if this app data store is transient and does not persist across runs."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def wheel_image(self, for_py_version: str, name: str) -> Path:
|
||||
"""Return the path to a cached wheel image.
|
||||
|
||||
:param for_py_version: the target Python version string
|
||||
:param name: the package name
|
||||
|
||||
:returns: the path to the cached wheel
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@contextmanager
|
||||
def ensure_extracted(self, path: Path, to_folder: Path | None = None) -> Generator[Path]:
|
||||
"""Ensure a path is available on disk, extracting from zipapp if needed.
|
||||
|
||||
:param path: the path to ensure is available
|
||||
:param to_folder: optional target directory for extraction
|
||||
|
||||
:returns: yields the usable path on disk
|
||||
|
||||
"""
|
||||
if IS_ZIPAPP:
|
||||
with self.extract(path, to_folder) as result:
|
||||
yield result
|
||||
else:
|
||||
yield path
|
||||
|
||||
@abstractmethod
|
||||
@contextmanager
|
||||
def extract(self, path: Path, to_folder: Path | None) -> Generator[Path]:
|
||||
"""Extract a path from the zipapp to a location on disk.
|
||||
|
||||
:param path: the path to extract
|
||||
:param to_folder: optional target directory
|
||||
|
||||
:returns: yields the extracted path
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
@contextmanager
|
||||
def locked(self, path: Path) -> Generator[None]:
|
||||
"""Acquire an exclusive lock on the given path.
|
||||
|
||||
:param path: the path to lock
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ContentStore(ABC):
|
||||
"""A store for reading and writing cached content."""
|
||||
|
||||
@abstractmethod
|
||||
def exists(self) -> bool:
|
||||
"""Check if the stored content exists.
|
||||
|
||||
:returns: ``True`` if content exists
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def read(self) -> Any: # noqa: ANN401
|
||||
"""Read the stored content.
|
||||
|
||||
:returns: the stored content
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def write(self, content: Any) -> None: # noqa: ANN401
|
||||
"""Write content to the store.
|
||||
|
||||
:param content: the content to write
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def remove(self) -> None:
|
||||
"""Remove the stored content."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
@contextmanager
|
||||
def locked(self) -> Generator[None]:
|
||||
"""Acquire an exclusive lock on this content store."""
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AppData",
|
||||
"ContentStore",
|
||||
]
|
||||
@@ -0,0 +1,78 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import AppData, ContentStore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
from typing import Any, NoReturn
|
||||
|
||||
|
||||
class AppDataDisabled(AppData):
|
||||
"""No application cache available (most likely as we don't have write permissions)."""
|
||||
|
||||
transient = True
|
||||
can_update = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
error = RuntimeError("no app data folder available, probably no write access to the folder")
|
||||
|
||||
def close(self) -> None:
|
||||
"""Do nothing."""
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Do nothing."""
|
||||
|
||||
def py_info(self, path: Path) -> ContentStoreNA: # noqa: ARG002
|
||||
return ContentStoreNA()
|
||||
|
||||
def embed_update_log(self, distribution: str, for_py_version: str) -> ContentStoreNA: # noqa: ARG002
|
||||
return ContentStoreNA()
|
||||
|
||||
def extract(self, path: Path, to_folder: Path | None) -> NoReturn: # noqa: ARG002
|
||||
raise self.error
|
||||
|
||||
@contextmanager
|
||||
def locked(self, path: Path) -> Generator[None]: # noqa: ARG002
|
||||
"""Do nothing."""
|
||||
yield
|
||||
|
||||
@property
|
||||
def house(self) -> NoReturn:
|
||||
raise self.error
|
||||
|
||||
def wheel_image(self, for_py_version: str, name: str) -> NoReturn: # noqa: ARG002
|
||||
raise self.error
|
||||
|
||||
def py_info_clear(self) -> None:
|
||||
"""Nothing to clear."""
|
||||
|
||||
|
||||
class ContentStoreNA(ContentStore):
|
||||
def exists(self) -> bool:
|
||||
return False
|
||||
|
||||
def read(self) -> None:
|
||||
"""Nothing to read."""
|
||||
return
|
||||
|
||||
def write(self, content: Any) -> None: # noqa: ANN401
|
||||
"""Nothing to write."""
|
||||
|
||||
def remove(self) -> None:
|
||||
"""Nothing to remove."""
|
||||
|
||||
@contextmanager
|
||||
def locked(self) -> Generator[None]:
|
||||
yield
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AppDataDisabled",
|
||||
"ContentStoreNA",
|
||||
]
|
||||
@@ -0,0 +1,47 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.util.lock import NoOpFileLock
|
||||
|
||||
from .via_disk_folder import AppDataDiskFolder, PyInfoStoreDisk
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
class ReadOnlyAppData(AppDataDiskFolder):
|
||||
can_update = False
|
||||
|
||||
def __init__(self, folder: str) -> None:
|
||||
if not os.path.isdir(folder):
|
||||
msg = f"read-only app data directory {folder} does not exist"
|
||||
raise RuntimeError(msg)
|
||||
super().__init__(folder)
|
||||
self.lock = NoOpFileLock(folder)
|
||||
|
||||
def reset(self) -> None:
|
||||
msg = "read-only app data does not support reset"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def py_info_clear(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def py_info(self, path: Path) -> _PyInfoStoreDiskReadOnly:
|
||||
return _PyInfoStoreDiskReadOnly(self.py_info_at, path)
|
||||
|
||||
def embed_update_log(self, distribution: str, for_py_version: str) -> NoReturn:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class _PyInfoStoreDiskReadOnly(PyInfoStoreDisk):
|
||||
def write(self, content: str) -> NoReturn: # noqa: ARG002
|
||||
msg = "read-only app data python info cannot be updated"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ReadOnlyAppData",
|
||||
]
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
r"""A rough layout of the current storage goes as:
|
||||
|
||||
::
|
||||
|
||||
virtualenv-app-data
|
||||
├── py - <version> <cache information about python interpreters>
|
||||
│ └── *.json/lock
|
||||
├── wheel <cache wheels used for seeding>
|
||||
│ ├── house
|
||||
│ │ └── *.whl <wheels downloaded go here>
|
||||
│ └── <python major.minor> -> 3.9
|
||||
│ ├── img-<version>
|
||||
│ │ └── image
|
||||
│ │ └── <install class> -> CopyPipInstall / SymlinkPipInstall
|
||||
│ │ └── <wheel name> -> pip-20.1.1-py2.py3-none-any
|
||||
│ └── embed
|
||||
│ └── 3 -> json format versioning
|
||||
│ └── *.json -> for every distribution contains data about newer embed versions and releases
|
||||
└─── unzip <in zip app we cannot refer to some internal files, so first extract them>
|
||||
└── <virtualenv version>
|
||||
├── py_info.py
|
||||
├── debug.py
|
||||
└── _virtualenv.py
|
||||
|
||||
""" # noqa: D415
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from abc import ABC
|
||||
from contextlib import contextmanager, suppress
|
||||
from hashlib import sha256
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from virtualenv.util.lock import ReentrantFileLock
|
||||
from virtualenv.util.path import safe_delete
|
||||
from virtualenv.util.zipapp import extract
|
||||
from virtualenv.version import __version__
|
||||
|
||||
from .base import AppData, ContentStore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AppDataDiskFolder(AppData):
|
||||
"""Store the application data on the disk within a folder layout."""
|
||||
|
||||
transient = False
|
||||
can_update = True
|
||||
|
||||
def __init__(self, folder: str) -> None:
|
||||
self.lock = ReentrantFileLock(folder)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{type(self).__name__}({self.lock.path})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.lock.path)
|
||||
|
||||
def reset(self) -> None:
|
||||
LOGGER.debug("reset app data folder %s", self.lock.path)
|
||||
safe_delete(self.lock.path)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Do nothing."""
|
||||
|
||||
@contextmanager
|
||||
def locked(self, path: Path) -> Generator[None]:
|
||||
path_lock = self.lock / path # ty: ignore[unsupported-operator]
|
||||
with path_lock:
|
||||
yield path_lock.path
|
||||
|
||||
@contextmanager
|
||||
def extract(self, path: Path, to_folder: Path | None) -> Generator[Path]:
|
||||
root = ReentrantFileLock(to_folder()) if to_folder is not None else self.lock / "unzip" / __version__ # ty: ignore[call-non-callable]
|
||||
with root.lock_for_key(path.name):
|
||||
dest = root.path / path.name
|
||||
if not dest.exists():
|
||||
extract(path, dest)
|
||||
yield dest
|
||||
|
||||
@property
|
||||
def py_info_at(self) -> ReentrantFileLock:
|
||||
return self.lock / "py_info" / "5" # ty: ignore[invalid-return-type]
|
||||
|
||||
def py_info(self, path: Path) -> PyInfoStoreDisk:
|
||||
return PyInfoStoreDisk(self.py_info_at, path)
|
||||
|
||||
def py_info_clear(self) -> None:
|
||||
"""clear py info."""
|
||||
py_info_folder = self.py_info_at
|
||||
with py_info_folder:
|
||||
for filename in py_info_folder.path.iterdir():
|
||||
if filename.suffix == ".json":
|
||||
with py_info_folder.lock_for_key(filename.stem):
|
||||
if filename.exists():
|
||||
filename.unlink()
|
||||
|
||||
def embed_update_log(self, distribution: str, for_py_version: str) -> EmbedDistributionUpdateStoreDisk:
|
||||
return EmbedDistributionUpdateStoreDisk(self.lock / "wheel" / for_py_version / "embed" / "3", distribution) # ty: ignore[invalid-argument-type]
|
||||
|
||||
@property
|
||||
def house(self) -> Path:
|
||||
path = self.lock.path / "wheel" / "house"
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
|
||||
def wheel_image(self, for_py_version: str, name: str) -> Path:
|
||||
return self.lock.path / "wheel" / for_py_version / "image" / "1" / name
|
||||
|
||||
|
||||
class JSONStoreDisk(ContentStore, ABC):
|
||||
def __init__(self, in_folder: ReentrantFileLock, key: str, msg_args: tuple[str, ...]) -> None:
|
||||
self.in_folder = in_folder
|
||||
self.key = key
|
||||
self.msg_args = (*msg_args, self.file)
|
||||
|
||||
@property
|
||||
def file(self) -> Path:
|
||||
return self.in_folder.path / f"{self.key}.json"
|
||||
|
||||
def exists(self) -> bool:
|
||||
return self.file.exists()
|
||||
|
||||
def read(self) -> Any: # noqa: ANN401
|
||||
data, bad_format = None, False
|
||||
try:
|
||||
data = json.loads(self.file.read_text(encoding="utf-8"))
|
||||
except ValueError:
|
||||
bad_format = True
|
||||
except Exception: # noqa: BLE001, S110
|
||||
pass
|
||||
else:
|
||||
LOGGER.debug("got %s %s from %s", *self.msg_args)
|
||||
return data
|
||||
if bad_format:
|
||||
with suppress(OSError): # reading and writing on the same file may cause race on multiple processes
|
||||
self.remove()
|
||||
return None
|
||||
|
||||
def remove(self) -> None:
|
||||
self.file.unlink()
|
||||
LOGGER.debug("removed %s %s at %s", *self.msg_args)
|
||||
|
||||
@contextmanager
|
||||
def locked(self) -> Generator[None]:
|
||||
with self.in_folder.lock_for_key(self.key):
|
||||
yield
|
||||
|
||||
def write(self, content: Any) -> None: # noqa: ANN401
|
||||
folder = self.file.parent
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
self.file.write_text(json.dumps(content, sort_keys=True, indent=2), encoding="utf-8")
|
||||
LOGGER.debug("wrote %s %s at %s", *self.msg_args)
|
||||
|
||||
|
||||
class PyInfoStoreDisk(JSONStoreDisk):
|
||||
def __init__(self, in_folder: ReentrantFileLock, path: Path) -> None:
|
||||
key = sha256(str(path).encode("utf-8")).hexdigest()
|
||||
super().__init__(in_folder, key, ("python info of", path)) # ty: ignore[invalid-argument-type]
|
||||
|
||||
|
||||
class EmbedDistributionUpdateStoreDisk(JSONStoreDisk):
|
||||
def __init__(self, in_folder: ReentrantFileLock, distribution: str) -> None:
|
||||
super().__init__(
|
||||
in_folder,
|
||||
distribution,
|
||||
("embed update of distribution", distribution),
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AppDataDiskFolder",
|
||||
"JSONStoreDisk",
|
||||
"PyInfoStoreDisk",
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from tempfile import mkdtemp
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.util.path import safe_delete
|
||||
|
||||
from .via_disk_folder import AppDataDiskFolder
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import NoReturn
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TempAppData(AppDataDiskFolder):
|
||||
transient = True
|
||||
can_update = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(folder=mkdtemp())
|
||||
LOGGER.debug("created temporary app data folder %s", self.lock.path)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""This is a temporary folder, is already empty to start with."""
|
||||
|
||||
def close(self) -> None:
|
||||
LOGGER.debug("remove temporary app data folder %s", self.lock.path)
|
||||
safe_delete(self.lock.path)
|
||||
|
||||
def embed_update_log(self, distribution: str, for_py_version: str) -> NoReturn:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TempAppData",
|
||||
]
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,160 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
|
||||
from collections import OrderedDict
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Action
|
||||
from collections.abc import Mapping, Sequence
|
||||
|
||||
from virtualenv.config.convert import get_type
|
||||
from virtualenv.config.env_var import get_env_var
|
||||
from virtualenv.config.ini import IniConfig
|
||||
|
||||
|
||||
class VirtualEnvOptions(Namespace):
|
||||
def __init__(self, **kwargs: Any) -> None: # noqa: ANN401
|
||||
super().__init__(**kwargs)
|
||||
self._src: str | None = None
|
||||
self._sources: dict[str, str] = {}
|
||||
|
||||
def set_src(self, key: str, value: Any, src: str) -> None: # noqa: ANN401
|
||||
"""Set an option value and record where it came from.
|
||||
|
||||
:param key: the option name
|
||||
:param value: the option value
|
||||
:param src: the source of the value (e.g. ``"cli"``, ``"env var"``, ``"default"``)
|
||||
|
||||
"""
|
||||
setattr(self, key, value)
|
||||
if src.startswith("env var"):
|
||||
src = "env var"
|
||||
self._sources[key] = src
|
||||
|
||||
def __setattr__(self, key: str, value: Any) -> None: # noqa: ANN401
|
||||
if (src := getattr(self, "_src", None)) is not None:
|
||||
self._sources[key] = src
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def get_source(self, key: str) -> str | None:
|
||||
"""Return the source that provided a given option value.
|
||||
|
||||
:param key: the option name
|
||||
|
||||
:returns: the source string (e.g. ``"cli"``, ``"env var"``, ``"default"``), or ``None`` if not tracked
|
||||
|
||||
"""
|
||||
return self._sources.get(key)
|
||||
|
||||
@property
|
||||
def verbosity(self) -> int | None:
|
||||
"""The verbosity level, computed as ``verbose - quiet``, clamped to zero.
|
||||
|
||||
:returns: the verbosity level, or ``None`` if neither ``--verbose`` nor ``--quiet`` has been parsed yet
|
||||
|
||||
"""
|
||||
if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
|
||||
return None
|
||||
return max(self.verbose - self.quiet, 0)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{type(self).__name__}({', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))})"
|
||||
|
||||
|
||||
class VirtualEnvConfigParser(ArgumentParser):
|
||||
"""Custom option parser which updates its defaults by checking the configuration files and environmental vars."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
options: VirtualEnvOptions | None = None,
|
||||
env: Mapping[str, str] | None = None,
|
||||
*args: Any, # noqa: ANN401
|
||||
**kwargs: Any, # noqa: ANN401
|
||||
) -> None:
|
||||
env = os.environ if env is None else env
|
||||
self.file_config = IniConfig(env)
|
||||
self.epilog_list = []
|
||||
self.env = env
|
||||
kwargs["epilog"] = self.file_config.epilog
|
||||
kwargs["add_help"] = False
|
||||
kwargs["formatter_class"] = HelpFormatter
|
||||
kwargs["prog"] = "virtualenv"
|
||||
super().__init__(*args, **kwargs)
|
||||
self._fixed = set()
|
||||
if options is not None and not isinstance(options, VirtualEnvOptions):
|
||||
msg = "options must be of type VirtualEnvOptions"
|
||||
raise TypeError(msg)
|
||||
self.options = VirtualEnvOptions() if options is None else options
|
||||
self._interpreter = None
|
||||
self._app_data = None
|
||||
|
||||
def _fix_defaults(self) -> None:
|
||||
for action in self._actions:
|
||||
action_id = id(action)
|
||||
if action_id not in self._fixed:
|
||||
self._fix_default(action)
|
||||
self._fixed.add(action_id)
|
||||
|
||||
def _fix_default(self, action: Action) -> None:
|
||||
if hasattr(action, "default") and hasattr(action, "dest") and action.default != SUPPRESS:
|
||||
as_type = get_type(action)
|
||||
names = OrderedDict((i.lstrip("-").replace("-", "_"), None) for i in action.option_strings)
|
||||
outcome = None
|
||||
for name in names:
|
||||
outcome = get_env_var(name, as_type, self.env)
|
||||
if outcome is not None:
|
||||
break
|
||||
if outcome is None and self.file_config:
|
||||
for name in names:
|
||||
outcome = self.file_config.get(name, as_type)
|
||||
if outcome is not None:
|
||||
break
|
||||
if outcome is not None:
|
||||
action.default, default_source = outcome
|
||||
vars(action)["default_source"] = default_source
|
||||
else:
|
||||
outcome = action.default, "default"
|
||||
self.options.set_src(action.dest, *outcome)
|
||||
|
||||
def enable_help(self) -> None:
|
||||
self._fix_defaults()
|
||||
self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")
|
||||
|
||||
def parse_known_args( # ty: ignore[invalid-method-override]
|
||||
self, args: Sequence[str] | None = None, namespace: VirtualEnvOptions | None = None
|
||||
) -> tuple[VirtualEnvOptions, list[str]]:
|
||||
if namespace is None:
|
||||
namespace = self.options
|
||||
elif namespace is not self.options:
|
||||
msg = "can only pass in parser.options"
|
||||
raise ValueError(msg)
|
||||
self._fix_defaults()
|
||||
self.options._src = "cli" # noqa: SLF001
|
||||
try:
|
||||
namespace.env = self.env
|
||||
return super().parse_known_args(args, namespace=namespace)
|
||||
finally:
|
||||
self.options._src = None # noqa: SLF001
|
||||
|
||||
|
||||
class HelpFormatter(ArgumentDefaultsHelpFormatter):
|
||||
def __init__(self, prog: str, **kwargs: Any) -> None: # noqa: ANN401
|
||||
super().__init__(prog, max_help_position=32, width=shutil.get_terminal_size().columns, **kwargs)
|
||||
|
||||
def _get_help_string(self, action: Action) -> str | None:
|
||||
text = super()._get_help_string(action)
|
||||
if text is not None and hasattr(action, "default_source"):
|
||||
default = " (default: %(default)s)"
|
||||
if text.endswith(default):
|
||||
text = f"{text[: -len(default)]} (default: %(default)s -> from %(default_source)s)"
|
||||
return text
|
||||
|
||||
|
||||
__all__ = [
|
||||
"HelpFormatter",
|
||||
"VirtualEnvConfigParser",
|
||||
"VirtualEnvOptions",
|
||||
]
|
||||
@@ -0,0 +1,105 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Action
|
||||
from typing import Any
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TypeData:
|
||||
def __init__(self, default_type: type, as_type: type) -> None:
|
||||
self.default_type = default_type
|
||||
self.as_type = as_type
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(base={self.default_type}, as={self.as_type})"
|
||||
|
||||
def convert(self, value: str) -> Any: # noqa: ANN401
|
||||
return self.default_type(value)
|
||||
|
||||
|
||||
class BoolType(TypeData):
|
||||
BOOLEAN_STATES: ClassVar[dict[str, bool]] = {
|
||||
"1": True,
|
||||
"yes": True,
|
||||
"true": True,
|
||||
"on": True,
|
||||
"0": False,
|
||||
"no": False,
|
||||
"false": False,
|
||||
"off": False,
|
||||
}
|
||||
|
||||
def convert(self, value: str) -> bool:
|
||||
if value.lower() not in self.BOOLEAN_STATES:
|
||||
msg = f"Not a boolean: {value}"
|
||||
raise ValueError(msg)
|
||||
return self.BOOLEAN_STATES[value.lower()]
|
||||
|
||||
|
||||
class NoneType(TypeData):
|
||||
def convert(self, value: str) -> str | None:
|
||||
if not value:
|
||||
return None
|
||||
return str(value)
|
||||
|
||||
|
||||
class ListType(TypeData):
|
||||
def _validate(self) -> None:
|
||||
"""no op."""
|
||||
|
||||
def convert(self, value: str | list[str], flatten: bool = True) -> list[Any]: # noqa: ARG002, FBT002
|
||||
values = self.split_values(value)
|
||||
result = []
|
||||
for a_value in values:
|
||||
sub_values = a_value.split(os.pathsep)
|
||||
result.extend(sub_values)
|
||||
return [self.as_type(i) for i in result]
|
||||
|
||||
def split_values(self, value: str | bytes | list[str]) -> list[str]:
|
||||
"""Split the provided value into a list.
|
||||
|
||||
First this is done by newlines. If there were no newlines in the text, then we next try to split by comma.
|
||||
|
||||
"""
|
||||
if isinstance(value, (str, bytes)):
|
||||
# Use `splitlines` rather than a custom check for whether there is
|
||||
# more than one line. This ensures that the full `splitlines()`
|
||||
# logic is supported here.
|
||||
values = value.splitlines()
|
||||
if len(values) <= 1:
|
||||
values = value.split(",") # ty: ignore[invalid-argument-type]
|
||||
values = filter(None, [x.strip() for x in values])
|
||||
else:
|
||||
values = list(value)
|
||||
|
||||
return values # ty: ignore[invalid-return-type]
|
||||
|
||||
|
||||
def convert(value: str, as_type: TypeData, source: str) -> Any: # noqa: ANN401
|
||||
"""Convert the value as a given type where the value comes from the given source."""
|
||||
try:
|
||||
return as_type.convert(value)
|
||||
except Exception as exception:
|
||||
LOGGER.warning("%s failed to convert %r as %r because %r", source, value, as_type, exception)
|
||||
raise
|
||||
|
||||
|
||||
_CONVERT = {bool: BoolType, type(None): NoneType, list: ListType}
|
||||
|
||||
|
||||
def get_type(action: Action) -> TypeData:
|
||||
default_type = type(action.default)
|
||||
as_type = default_type if action.type is None else action.type
|
||||
return _CONVERT.get(default_type, TypeData)(default_type, as_type) # ty: ignore[invalid-argument-type]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"convert",
|
||||
"get_type",
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .convert import convert
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from .convert import TypeData
|
||||
|
||||
|
||||
def get_env_var(key: str, as_type: TypeData, env: Mapping[str, str]) -> tuple[Any, str] | None:
|
||||
"""Get the environment variable option.
|
||||
|
||||
:param key: the config key requested
|
||||
:param as_type: the type we would like to convert it to
|
||||
:param env: environment variables to use
|
||||
|
||||
:returns: the converted value and source, or None if not set
|
||||
|
||||
"""
|
||||
environ_key = f"VIRTUALENV_{key.upper()}"
|
||||
if env.get(environ_key):
|
||||
value = env[environ_key]
|
||||
|
||||
with suppress(Exception): # note the converter already logs a warning when failures happen
|
||||
source = f"env var {environ_key}"
|
||||
as_type = convert(value, as_type, source)
|
||||
return as_type, source
|
||||
return None
|
||||
|
||||
|
||||
__all__ = [
|
||||
"get_env_var",
|
||||
]
|
||||
@@ -0,0 +1,83 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
from platformdirs import user_config_dir
|
||||
|
||||
from .convert import convert
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from .convert import TypeData
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IniConfig:
|
||||
VIRTUALENV_CONFIG_FILE_ENV_VAR: ClassVar[str] = "VIRTUALENV_CONFIG_FILE"
|
||||
STATE: ClassVar[dict[bool | None, str]] = {None: "failed to parse", True: "active", False: "missing"}
|
||||
|
||||
section = "virtualenv"
|
||||
|
||||
def __init__(self, env: Mapping[str, str] | None = None) -> None:
|
||||
env = os.environ if env is None else env
|
||||
config_file = env.get(self.VIRTUALENV_CONFIG_FILE_ENV_VAR, None)
|
||||
self.is_env_var = config_file is not None
|
||||
if config_file is None:
|
||||
config_file = Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini"
|
||||
else:
|
||||
config_file = Path(config_file)
|
||||
self.config_file = config_file
|
||||
self._cache = {}
|
||||
|
||||
exception = None
|
||||
self.has_config_file = None
|
||||
try:
|
||||
self.has_config_file = self.config_file.exists()
|
||||
except OSError as exc:
|
||||
exception = exc
|
||||
else:
|
||||
if self.has_config_file:
|
||||
self.config_file = self.config_file.resolve()
|
||||
self.config_parser = ConfigParser()
|
||||
try:
|
||||
self._load()
|
||||
self.has_virtualenv_section = self.config_parser.has_section(self.section)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
exception = exc
|
||||
if exception is not None:
|
||||
LOGGER.error("failed to read config file %s because %r", config_file, exception)
|
||||
|
||||
def _load(self) -> None:
|
||||
with self.config_file.open("rt", encoding="utf-8") as file_handler:
|
||||
return self.config_parser.read_file(file_handler)
|
||||
|
||||
def get(self, key: str, as_type: TypeData) -> tuple[Any, str] | None:
|
||||
cache_key = key, as_type
|
||||
if cache_key in self._cache:
|
||||
return self._cache[cache_key]
|
||||
try:
|
||||
source = "file"
|
||||
raw_value = self.config_parser.get(self.section, key.lower())
|
||||
value = convert(raw_value, as_type, source)
|
||||
result = value, source
|
||||
except Exception: # noqa: BLE001
|
||||
result = None
|
||||
self._cache[cache_key] = result
|
||||
return result
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.has_config_file) and bool(self.has_virtualenv_section)
|
||||
|
||||
@property
|
||||
def epilog(self) -> str:
|
||||
return (
|
||||
f"\nconfig file {self.config_file} {self.STATE[self.has_config_file]} "
|
||||
f"(change{'d' if self.is_env_var else ''} via env var {self.VIRTUALENV_CONFIG_FILE_ENV_VAR})"
|
||||
)
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,295 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
from abc import ABC, abstractmethod
|
||||
from argparse import ArgumentTypeError
|
||||
from ast import literal_eval
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import ArgumentParser
|
||||
from typing import Any, NoReturn
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
from virtualenv.app_data.base import AppData
|
||||
from virtualenv.config.cli.parser import VirtualEnvOptions
|
||||
|
||||
from os.path import commonpath
|
||||
|
||||
from virtualenv.util.path import safe_delete
|
||||
from virtualenv.util.subprocess import LogCmd, run_cmd
|
||||
from virtualenv.version import __version__
|
||||
|
||||
from .pyenv_cfg import PyEnvCfg
|
||||
|
||||
HERE = Path(os.path.abspath(__file__)).parent
|
||||
DEBUG_SCRIPT = HERE / "debug.py"
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreatorMeta:
|
||||
def __init__(self) -> None:
|
||||
self.error = None
|
||||
|
||||
|
||||
class Creator(ABC):
|
||||
"""A class that given a python Interpreter creates a virtual environment."""
|
||||
|
||||
def __init__(self, options: VirtualEnvOptions, interpreter: PythonInfo) -> None:
|
||||
"""Construct a new virtual environment creator.
|
||||
|
||||
:param options: the CLI option as parsed from :meth:`add_parser_arguments`
|
||||
:param interpreter: the interpreter to create virtual environment from
|
||||
|
||||
"""
|
||||
self.interpreter = interpreter
|
||||
self._debug = None
|
||||
self.dest = Path(options.dest)
|
||||
self.clear = options.clear
|
||||
self.no_vcs_ignore = options.no_vcs_ignore
|
||||
self.pyenv_cfg = PyEnvCfg.from_folder(self.dest)
|
||||
self.app_data = options.app_data
|
||||
self.env = options.env
|
||||
self.prompt = getattr(options, "prompt", None)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@property
|
||||
def exe(self) -> Path: ...
|
||||
|
||||
@property
|
||||
def env_name(self) -> str: ...
|
||||
|
||||
@property
|
||||
def bin_dir(self) -> Path: ...
|
||||
|
||||
@property
|
||||
def script_dir(self) -> Path: ...
|
||||
|
||||
@property
|
||||
def libs(self) -> list[Path]: ...
|
||||
|
||||
@property
|
||||
def purelib(self) -> Path: ...
|
||||
|
||||
@property
|
||||
def platlib(self) -> Path: ...
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({', '.join(f'{k}={v}' for k, v in self._args())})"
|
||||
|
||||
def _args(self) -> list[tuple[str, Any]]:
|
||||
return [
|
||||
("dest", str(self.dest)),
|
||||
("clear", self.clear),
|
||||
("no_vcs_ignore", self.no_vcs_ignore),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def can_create(cls, interpreter: PythonInfo) -> CreatorMeta | bool | None: # noqa: ARG003
|
||||
"""Determine if we can create a virtual environment.
|
||||
|
||||
:param interpreter: the interpreter in question
|
||||
|
||||
:returns: ``None`` if we can't create, any other object otherwise that will be forwarded to
|
||||
:meth:`add_parser_arguments`
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(
|
||||
cls,
|
||||
parser: ArgumentParser,
|
||||
interpreter: PythonInfo, # noqa: ARG003
|
||||
meta: CreatorMeta, # noqa: ARG003
|
||||
app_data: AppData, # noqa: ARG003
|
||||
) -> None:
|
||||
"""Add CLI arguments for the creator.
|
||||
|
||||
:param parser: the CLI parser
|
||||
:param app_data: the application data folder
|
||||
:param interpreter: the interpreter we're asked to create virtual environment for
|
||||
:param meta: value as returned by :meth:`can_create`
|
||||
|
||||
"""
|
||||
parser.add_argument(
|
||||
"dest",
|
||||
help="directory to create virtualenv at",
|
||||
type=cls.validate_dest,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clear",
|
||||
dest="clear",
|
||||
action="store_true",
|
||||
help="remove the destination directory if exist before starting (will overwrite files otherwise)",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-vcs-ignore",
|
||||
dest="no_vcs_ignore",
|
||||
action="store_true",
|
||||
help="don't create VCS ignore directive in the destination directory",
|
||||
default=False,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def create(self) -> None:
|
||||
"""Perform the virtual environment creation."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def validate_dest(cls, raw_value: str) -> str: # noqa: C901
|
||||
"""No path separator in the path, valid chars and must be write-able."""
|
||||
|
||||
def non_write_able(dest: Path, value: Path) -> NoReturn:
|
||||
common = Path(commonpath([str(value), str(dest)]))
|
||||
msg = f"the destination {dest.relative_to(common)} is not write-able at {common}"
|
||||
raise ArgumentTypeError(msg)
|
||||
|
||||
# the file system must be able to encode
|
||||
# note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/
|
||||
encoding = sys.getfilesystemencoding()
|
||||
refused = OrderedDict()
|
||||
kwargs = {"errors": "ignore"} if encoding != "mbcs" else {}
|
||||
for char in str(raw_value):
|
||||
try:
|
||||
trip = char.encode(encoding, **kwargs).decode(encoding)
|
||||
if trip == char:
|
||||
continue
|
||||
raise ValueError(trip) # noqa: TRY301
|
||||
except ValueError:
|
||||
refused[char] = None
|
||||
if refused:
|
||||
bad = "".join(refused.keys())
|
||||
msg = f"the file system codec ({encoding}) cannot handle characters {bad!r} within {raw_value!r}"
|
||||
raise ArgumentTypeError(msg)
|
||||
if os.pathsep in raw_value:
|
||||
msg = (
|
||||
f"destination {raw_value!r} must not contain the path separator ({os.pathsep})"
|
||||
f" as this would break the activation scripts"
|
||||
)
|
||||
raise ArgumentTypeError(msg)
|
||||
|
||||
value = Path(raw_value)
|
||||
if value.exists() and value.is_file():
|
||||
msg = f"the destination {value} already exists and is a file"
|
||||
raise ArgumentTypeError(msg)
|
||||
dest = Path(os.path.abspath(str(value))).resolve() # on Windows absolute does not imply resolve so use both
|
||||
value = dest
|
||||
while dest:
|
||||
if dest.exists():
|
||||
if os.access(str(dest), os.W_OK):
|
||||
break
|
||||
non_write_able(dest, value)
|
||||
base, _ = dest.parent, dest.name
|
||||
if base == dest:
|
||||
non_write_able(dest, value) # pragma: no cover
|
||||
dest = base
|
||||
return str(value)
|
||||
|
||||
def run(self) -> None:
|
||||
if self.dest.exists() and self.clear:
|
||||
LOGGER.debug("delete %s", self.dest)
|
||||
safe_delete(self.dest)
|
||||
self.create()
|
||||
self.add_cachedir_tag()
|
||||
self.set_pyenv_cfg()
|
||||
if not self.no_vcs_ignore:
|
||||
self.setup_ignore_vcs()
|
||||
|
||||
def add_cachedir_tag(self) -> None:
|
||||
"""Generate a file indicating that this is not meant to be backed up."""
|
||||
cachedir_tag_file = self.dest / "CACHEDIR.TAG"
|
||||
if not cachedir_tag_file.exists():
|
||||
cachedir_tag_text = textwrap.dedent("""
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by Python virtualenv.
|
||||
# For information about cache directory tags, see:
|
||||
# https://bford.info/cachedir/
|
||||
""").strip()
|
||||
cachedir_tag_file.write_text(cachedir_tag_text, encoding="utf-8")
|
||||
|
||||
def set_pyenv_cfg(self) -> None:
|
||||
self.pyenv_cfg.content = OrderedDict()
|
||||
system_executable = self.interpreter.system_executable or self.interpreter.executable
|
||||
assert system_executable is not None # noqa: S101
|
||||
self.pyenv_cfg["home"] = os.path.dirname(os.path.abspath(system_executable))
|
||||
self.pyenv_cfg["implementation"] = self.interpreter.implementation
|
||||
self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info)
|
||||
self.pyenv_cfg["version"] = ".".join(str(i) for i in self.interpreter.version_info[:3])
|
||||
self.pyenv_cfg["executable"] = os.path.realpath(system_executable)
|
||||
self.pyenv_cfg["command"] = f"{sys.executable} -m virtualenv {self.dest}"
|
||||
self.pyenv_cfg["virtualenv"] = __version__
|
||||
if self.prompt is not None:
|
||||
prompt_value = os.path.basename(os.getcwd()) if self.prompt == "." else self.prompt
|
||||
self.pyenv_cfg["prompt"] = prompt_value
|
||||
|
||||
def setup_ignore_vcs(self) -> None:
|
||||
"""Generate ignore instructions for version control systems."""
|
||||
# mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs
|
||||
git_ignore = self.dest / ".gitignore"
|
||||
if not git_ignore.exists():
|
||||
git_ignore.write_text("# created by virtualenv automatically\n*\n", encoding="utf-8")
|
||||
# Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the
|
||||
# subinclude directive from root, at which point on might as well ignore the directory itself, see
|
||||
# https://www.selenic.com/mercurial/hgignore.5.html for more details
|
||||
# Bazaar - does not support ignore files in sub-directories, only at root level via .bzrignore
|
||||
# Subversion - does not support ignore files, requires direct manipulation with the svn tool
|
||||
|
||||
@property
|
||||
def debug(self) -> dict[str, Any] | None:
|
||||
"""Debug information about the virtual environment (only valid after :meth:`create` has run)."""
|
||||
if self._debug is None and self.exe is not None:
|
||||
self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data, self.env)
|
||||
return self._debug
|
||||
|
||||
@staticmethod
|
||||
def debug_script() -> Path:
|
||||
return DEBUG_SCRIPT
|
||||
|
||||
|
||||
def get_env_debug_info(env_exe: Path, debug_script: Path, app_data: AppData, env: dict[str, str]) -> dict[str, Any]:
|
||||
env = env.copy()
|
||||
env.pop("PYTHONPATH", None)
|
||||
|
||||
with app_data.ensure_extracted(debug_script) as debug_script_extracted:
|
||||
cmd = [str(env_exe), str(debug_script_extracted)]
|
||||
LOGGER.debug("debug via %r", LogCmd(cmd))
|
||||
code, out, err = run_cmd(cmd)
|
||||
|
||||
try:
|
||||
result = _parse_debug_output(code, out, err)
|
||||
except Exception as exception: # noqa: BLE001
|
||||
return {"out": out, "err": err, "returncode": code, "exception": repr(exception)}
|
||||
if "sys" in result and "path" in result["sys"]:
|
||||
del result["sys"]["path"][0]
|
||||
return result
|
||||
|
||||
|
||||
def _parse_debug_output(code: int, out: str, err: str) -> dict[str, Any]:
|
||||
if code != 0:
|
||||
if out:
|
||||
result = literal_eval(out)
|
||||
elif code == 2 and "file" in err: # noqa: PLR2004
|
||||
raise OSError(err)
|
||||
else:
|
||||
raise Exception(err) # noqa: TRY002
|
||||
else:
|
||||
result = json.loads(out)
|
||||
if err:
|
||||
result["err"] = err
|
||||
return result
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Creator",
|
||||
"CreatorMeta",
|
||||
]
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Inspect a target Python interpreter virtual environment wise."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys # built-in
|
||||
|
||||
|
||||
def encode_path(value: object) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
if not isinstance(value, (str, bytes)):
|
||||
value = repr(value) if isinstance(value, type) else repr(type(value))
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode(sys.getfilesystemencoding())
|
||||
return value
|
||||
|
||||
|
||||
def encode_list_path(value: list[object]) -> list[str | None]:
|
||||
return [encode_path(i) for i in value]
|
||||
|
||||
|
||||
def run() -> None:
|
||||
"""Print debug data about the virtual environment."""
|
||||
sys_info: dict[str, str | list[str | None] | None] = {}
|
||||
result: dict[str, str | dict[str, str | list[str | None] | None] | None] = {"sys": sys_info}
|
||||
path_keys = (
|
||||
"executable",
|
||||
"_base_executable",
|
||||
"prefix",
|
||||
"base_prefix",
|
||||
"exec_prefix",
|
||||
"base_exec_prefix",
|
||||
"path",
|
||||
"meta_path",
|
||||
)
|
||||
for key in path_keys:
|
||||
value = getattr(sys, key, None)
|
||||
value = encode_list_path(value) if isinstance(value, list) else encode_path(value)
|
||||
sys_info[key] = value
|
||||
sys_info["fs_encoding"] = sys.getfilesystemencoding()
|
||||
sys_info["io_encoding"] = getattr(sys.stdout, "encoding", None)
|
||||
result["version"] = sys.version
|
||||
|
||||
try:
|
||||
import sysconfig # noqa: PLC0415
|
||||
|
||||
result["makefile_filename"] = encode_path(sysconfig.get_makefile_filename())
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import os # landmark # noqa: PLC0415
|
||||
|
||||
result["os"] = repr(os)
|
||||
|
||||
try:
|
||||
import site # site # noqa: PLC0415
|
||||
|
||||
result["site"] = repr(site)
|
||||
except ImportError as exception: # pragma: no cover
|
||||
result["site"] = repr(exception) # pragma: no cover
|
||||
|
||||
try:
|
||||
import datetime # site # noqa: PLC0415
|
||||
|
||||
result["datetime"] = repr(datetime)
|
||||
except ImportError as exception: # pragma: no cover
|
||||
result["datetime"] = repr(exception) # pragma: no cover
|
||||
|
||||
try:
|
||||
import math # site # noqa: PLC0415
|
||||
|
||||
result["math"] = repr(math)
|
||||
except ImportError as exception: # pragma: no cover
|
||||
result["math"] = repr(exception) # pragma: no cover
|
||||
|
||||
# try to print out, this will validate if other core modules are available (json in this case)
|
||||
try:
|
||||
import json # noqa: PLC0415
|
||||
|
||||
result["json"] = repr(json)
|
||||
except ImportError as exception:
|
||||
result["json"] = repr(exception)
|
||||
else:
|
||||
try:
|
||||
content = json.dumps(result, indent=2)
|
||||
sys.stdout.write(content)
|
||||
except (ValueError, TypeError) as exception: # pragma: no cover
|
||||
sys.stderr.write(repr(exception))
|
||||
sys.stdout.write(repr(result)) # pragma: no cover
|
||||
raise SystemExit(1) # noqa: B904 # pragma: no cover
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,116 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.info import IS_WIN
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
|
||||
class Describe:
|
||||
"""Given a host interpreter tell us information about what the created interpreter might look like."""
|
||||
|
||||
suffix = ".exe" if IS_WIN else ""
|
||||
|
||||
def __init__(self, dest: Path, interpreter: PythonInfo) -> None:
|
||||
self.interpreter = interpreter
|
||||
self.dest = dest
|
||||
self._stdlib = None
|
||||
self._stdlib_platform = None
|
||||
self._system_stdlib = None
|
||||
self._conf_vars = None
|
||||
|
||||
@property
|
||||
def bin_dir(self) -> Path:
|
||||
return self.script_dir
|
||||
|
||||
@property
|
||||
def script_dir(self) -> Path:
|
||||
return self.dest / self.interpreter.install_path("scripts")
|
||||
|
||||
@property
|
||||
def purelib(self) -> Path:
|
||||
return self.dest / self.interpreter.install_path("purelib")
|
||||
|
||||
@property
|
||||
def platlib(self) -> Path:
|
||||
return self.dest / self.interpreter.install_path("platlib")
|
||||
|
||||
@property
|
||||
def libs(self) -> list[Path]:
|
||||
return list(OrderedDict(((self.platlib, None), (self.purelib, None))).keys())
|
||||
|
||||
@property
|
||||
def stdlib(self) -> Path:
|
||||
if self._stdlib is None:
|
||||
self._stdlib = Path(self.interpreter.sysconfig_path("stdlib", config_var=self._config_vars))
|
||||
return self._stdlib
|
||||
|
||||
@property
|
||||
def stdlib_platform(self) -> Path:
|
||||
if self._stdlib_platform is None:
|
||||
self._stdlib_platform = Path(self.interpreter.sysconfig_path("platstdlib", config_var=self._config_vars))
|
||||
return self._stdlib_platform
|
||||
|
||||
@property
|
||||
def _config_vars(self) -> dict[str, Any]:
|
||||
if self._conf_vars is None:
|
||||
self._conf_vars = self._calc_config_vars(self.dest)
|
||||
return self._conf_vars
|
||||
|
||||
def _calc_config_vars(self, to: Path) -> dict[str, Any]:
|
||||
sys_vars = self.interpreter.sysconfig_vars
|
||||
return {
|
||||
k: (to if isinstance(v, str) and v.startswith(self.interpreter.prefix) else v) for k, v in sys_vars.items()
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def can_describe(cls, interpreter: PythonInfo) -> bool: # noqa: ARG003
|
||||
"""Knows means it knows how the output will look."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def env_name(self) -> str:
|
||||
return self.dest.parts[-1]
|
||||
|
||||
@property
|
||||
def exe(self) -> Path:
|
||||
return self.bin_dir / f"{self.exe_stem()}{self.suffix}"
|
||||
|
||||
@classmethod
|
||||
def exe_stem(cls) -> str:
|
||||
"""Executable name without suffix - there seems to be no standard way to get this without creating it."""
|
||||
raise NotImplementedError
|
||||
|
||||
def script(self, name: str) -> Path:
|
||||
return self.script_dir / f"{name}{self.suffix}"
|
||||
|
||||
|
||||
class Python3Supports(Describe, ABC):
|
||||
pass
|
||||
|
||||
|
||||
class PosixSupports(Describe, ABC):
|
||||
@classmethod
|
||||
def can_describe(cls, interpreter: PythonInfo) -> bool:
|
||||
return interpreter.os == "posix" and super().can_describe(interpreter)
|
||||
|
||||
|
||||
class WindowsSupports(Describe, ABC):
|
||||
@classmethod
|
||||
def can_describe(cls, interpreter: PythonInfo) -> bool:
|
||||
return interpreter.os == "nt" and super().can_describe(interpreter)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Describe",
|
||||
"PosixSupports",
|
||||
"Python3Supports",
|
||||
"WindowsSupports",
|
||||
]
|
||||
@@ -0,0 +1,79 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PyEnvCfg:
|
||||
def __init__(self, content: OrderedDict[str, str], path: Path) -> None:
|
||||
self.content = content
|
||||
self.path = path
|
||||
|
||||
@classmethod
|
||||
def from_folder(cls, folder: Path) -> PyEnvCfg:
|
||||
return cls.from_file(folder / "pyvenv.cfg")
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path: Path) -> PyEnvCfg:
|
||||
content = cls._read_values(path) if path.exists() else OrderedDict()
|
||||
return PyEnvCfg(content, path)
|
||||
|
||||
@staticmethod
|
||||
def _read_values(path: Path) -> OrderedDict[str, str]:
|
||||
content = OrderedDict()
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
equals_at = line.index("=")
|
||||
key = line[:equals_at].strip()
|
||||
value = line[equals_at + 1 :].strip()
|
||||
if len(value) > 1 and value[0] in {"'", '"'} and value[0] == value[-1]:
|
||||
value = value[1:-1]
|
||||
content[key] = value
|
||||
return content
|
||||
|
||||
def write(self) -> None:
|
||||
LOGGER.debug("write %s", self.path)
|
||||
text = ""
|
||||
for key, value in self.content.items():
|
||||
# Use abspath to normalize relative paths but preserve symlinks (match venv behavior)
|
||||
# See issue #2770 - realpath resolves symlinks which breaks prefix symlinks
|
||||
if key == "prompt" and value:
|
||||
normalized_value = f'"{value}"'
|
||||
else:
|
||||
normalized_value = os.path.abspath(value) if value and os.path.exists(value) else value
|
||||
line = f"{key} = {normalized_value}"
|
||||
LOGGER.debug("\t%s", line)
|
||||
text += line
|
||||
text += "\n"
|
||||
self.path.write_text(text, encoding="utf-8")
|
||||
|
||||
def refresh(self) -> OrderedDict[str, str]:
|
||||
self.content = self._read_values(self.path)
|
||||
return self.content
|
||||
|
||||
def __setitem__(self, key: str, value: str) -> None:
|
||||
self.content[key] = value
|
||||
|
||||
def __getitem__(self, key: str) -> str:
|
||||
return self.content[key]
|
||||
|
||||
def __contains__(self, item: str) -> bool:
|
||||
return item in self.content
|
||||
|
||||
def update(self, other: dict[str, str]) -> PyEnvCfg:
|
||||
self.content.update(other)
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(path={self.path})"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PyEnvCfg",
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+114
@@ -0,0 +1,114 @@
|
||||
"""Patches that are applied at runtime to the virtual environment."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import types
|
||||
from collections.abc import Callable
|
||||
from importlib.machinery import ModuleSpec
|
||||
|
||||
VIRTUALENV_PATCH_FILE = os.path.abspath(__file__)
|
||||
|
||||
|
||||
def patch_dist(dist: types.ModuleType) -> None:
|
||||
"""Distutils allows user to configure some arguments via a configuration file: https://docs.python.org/3/install/index.html#distutils-configuration-files.
|
||||
|
||||
Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up.
|
||||
|
||||
"""
|
||||
# we cannot allow some install config as that would get packages installed outside of the virtual environment
|
||||
old_parse_config_files = dist.Distribution.parse_config_files
|
||||
|
||||
def parse_config_files(self, *args: object, **kwargs: object) -> object: # noqa: ANN001
|
||||
result = old_parse_config_files(self, *args, **kwargs)
|
||||
install = self.get_option_dict("install")
|
||||
|
||||
if "prefix" in install: # the prefix governs where to install the libraries
|
||||
install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix)
|
||||
for base in ("purelib", "platlib", "headers", "scripts", "data"):
|
||||
key = f"install_{base}"
|
||||
if key in install: # do not allow global configs to hijack venv paths
|
||||
install.pop(key, None)
|
||||
return result
|
||||
|
||||
dist.Distribution.parse_config_files = parse_config_files
|
||||
|
||||
|
||||
# Import hook that patches some modules to ignore configuration values that break package installation in case
|
||||
# of virtual environments.
|
||||
_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist"
|
||||
# https://docs.python.org/3/library/importlib.html#setting-up-an-importer
|
||||
|
||||
|
||||
class _Finder:
|
||||
"""A meta path finder that allows patching the imported distutils modules."""
|
||||
|
||||
fullname = None
|
||||
|
||||
# lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup,
|
||||
# because there are gevent-based applications that need to be first to import threading by themselves.
|
||||
# See https://github.com/pypa/virtualenv/issues/1895 for details.
|
||||
lock = [] # noqa: RUF012
|
||||
|
||||
def find_spec(self, fullname: str, path: object, target: object = None) -> ModuleSpec | None: # noqa: ARG002
|
||||
# Guard against race conditions during file rewrite by checking if _DISTUTILS_PATCH is defined.
|
||||
# This can happen when the file is being overwritten while it's being imported by another process.
|
||||
# See https://github.com/pypa/virtualenv/issues/2969 for details.
|
||||
try:
|
||||
distutils_patch = _DISTUTILS_PATCH
|
||||
except NameError:
|
||||
return None
|
||||
if fullname in distutils_patch and self.fullname is None:
|
||||
# initialize lock[0] lazily
|
||||
if len(self.lock) == 0:
|
||||
import threading # noqa: PLC0415
|
||||
|
||||
lock = threading.Lock()
|
||||
# there is possibility that two threads T1 and T2 are simultaneously running into find_spec,
|
||||
# observing .lock as empty, and further going into hereby initialization. However due to the GIL,
|
||||
# list.append() operation is atomic and this way only one of the threads will "win" to put the lock
|
||||
# - that every thread will use - into .lock[0].
|
||||
# https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
|
||||
self.lock.append(lock)
|
||||
|
||||
from functools import partial # noqa: PLC0415
|
||||
from importlib.util import find_spec # noqa: PLC0415
|
||||
|
||||
with self.lock[0]:
|
||||
self.fullname = fullname
|
||||
spec = None
|
||||
try:
|
||||
spec = find_spec(fullname, path) # ty: ignore[invalid-argument-type]
|
||||
finally:
|
||||
self.fullname = None
|
||||
if spec is not None:
|
||||
return self._patch_spec(spec, partial)
|
||||
return None
|
||||
|
||||
def _patch_spec(self, spec: ModuleSpec, partial: Callable[..., object]) -> ModuleSpec:
|
||||
old = getattr(spec.loader, "exec_module", None)
|
||||
if old is not None and old is not self.exec_module:
|
||||
try: # noqa: SIM105
|
||||
spec.loader.exec_module = partial(self.exec_module, old) # ty: ignore[invalid-assignment]
|
||||
except AttributeError:
|
||||
pass
|
||||
return spec
|
||||
|
||||
@staticmethod
|
||||
def exec_module(old: Callable[..., object], module: types.ModuleType) -> None:
|
||||
old(module)
|
||||
try:
|
||||
distutils_patch = _DISTUTILS_PATCH
|
||||
except NameError:
|
||||
return
|
||||
if module.__name__ in distutils_patch:
|
||||
with contextlib.suppress(NameError):
|
||||
patch_dist(module)
|
||||
|
||||
|
||||
sys.meta_path.insert(0, _Finder())
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from abc import ABC
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.create.creator import Creator, CreatorMeta
|
||||
from virtualenv.info import fs_supports_symlink
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import ArgumentParser
|
||||
from typing import Any
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
from virtualenv.app_data.base import AppData
|
||||
from virtualenv.config.cli.parser import VirtualEnvOptions
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ViaGlobalRefMeta(CreatorMeta):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.copy_error = None
|
||||
self.symlink_error = None
|
||||
if not fs_supports_symlink():
|
||||
self.symlink_error = "the filesystem does not supports symlink"
|
||||
|
||||
@property
|
||||
def can_copy(self) -> bool:
|
||||
return not self.copy_error
|
||||
|
||||
@property
|
||||
def can_symlink(self) -> bool:
|
||||
return not self.symlink_error
|
||||
|
||||
|
||||
class ViaGlobalRefApi(Creator, ABC):
|
||||
def __init__(self, options: VirtualEnvOptions, interpreter: PythonInfo) -> None:
|
||||
super().__init__(options, interpreter)
|
||||
self.symlinks = self._should_symlink(options)
|
||||
self.enable_system_site_package = options.system_site
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@property
|
||||
def purelib(self) -> Path: ...
|
||||
|
||||
@property
|
||||
def script_dir(self) -> Path: ...
|
||||
|
||||
@staticmethod
|
||||
def _should_symlink(options: VirtualEnvOptions) -> bool:
|
||||
# Priority of where the option is set to follow the order: CLI, env var, file, hardcoded.
|
||||
# If both set at same level prefers copy over symlink.
|
||||
copies, symlinks = getattr(options, "copies", False), getattr(options, "symlinks", False)
|
||||
copy_src, sym_src = options.get_source("copies"), options.get_source("symlinks")
|
||||
for level in ["cli", "env var", "file", "default"]:
|
||||
s_opt = symlinks if sym_src == level else None
|
||||
c_opt = copies if copy_src == level else None
|
||||
if s_opt is True and c_opt is True:
|
||||
return False
|
||||
if s_opt is True:
|
||||
return True
|
||||
if c_opt is True:
|
||||
return False
|
||||
return False # fallback to copy
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(
|
||||
cls, parser: ArgumentParser, interpreter: PythonInfo, meta: ViaGlobalRefMeta, app_data: AppData
|
||||
) -> None: # ty: ignore[invalid-method-override]
|
||||
super().add_parser_arguments(parser, interpreter, meta, app_data)
|
||||
parser.add_argument(
|
||||
"--system-site-packages",
|
||||
default=False,
|
||||
action="store_true",
|
||||
dest="system_site",
|
||||
help="give the virtual environment access to the system site-packages dir",
|
||||
)
|
||||
if not meta.can_symlink and not meta.can_copy:
|
||||
errors = []
|
||||
if meta.symlink_error:
|
||||
errors.append(f"symlink: {meta.symlink_error}")
|
||||
if meta.copy_error:
|
||||
errors.append(f"copy: {meta.copy_error}")
|
||||
msg = f"neither symlink or copy method supported: {', '.join(errors)}"
|
||||
raise RuntimeError(msg)
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
if meta.can_symlink:
|
||||
group.add_argument(
|
||||
"--symlinks",
|
||||
default=True,
|
||||
action="store_true",
|
||||
dest="symlinks",
|
||||
help="try to use symlinks rather than copies, when symlinks are not the default for the platform",
|
||||
)
|
||||
if meta.can_copy:
|
||||
group.add_argument(
|
||||
"--copies",
|
||||
"--always-copy",
|
||||
default=not meta.can_symlink,
|
||||
action="store_true",
|
||||
dest="copies",
|
||||
help="try to use copies rather than symlinks, even when symlinks are the default for the platform",
|
||||
)
|
||||
|
||||
def create(self) -> None:
|
||||
self.install_patch()
|
||||
|
||||
def install_patch(self) -> None:
|
||||
# Python 3.10+ ignores the distutils install config keys this patch guards against (pip does so by default,
|
||||
# setuptools and CPython's vendored distutils drop them too), so the runtime import hook only earns its
|
||||
# startup cost on 3.9. See https://github.com/pypa/virtualenv/issues/3181.
|
||||
if self.interpreter.version_info >= (3, 10):
|
||||
return
|
||||
text = self.env_patch_text()
|
||||
if text:
|
||||
pth = self.purelib / "_virtualenv.pth"
|
||||
LOGGER.debug("create virtualenv import hook file %s", pth)
|
||||
pth.write_text("import _virtualenv", encoding="utf-8")
|
||||
dest_path = self.purelib / "_virtualenv.py"
|
||||
LOGGER.debug("create %s", dest_path)
|
||||
dest_path.write_text(text, encoding="utf-8")
|
||||
|
||||
def env_patch_text(self) -> str:
|
||||
"""Patch the distutils package to not be derailed by its configuration files."""
|
||||
with self.app_data.ensure_extracted(Path(__file__).parent / "_virtualenv.py") as resolved_path:
|
||||
return resolved_path.read_text(encoding="utf-8")
|
||||
|
||||
def _args(self) -> list[tuple[str, Any]]:
|
||||
return [*super()._args(), ("global", self.enable_system_site_package)]
|
||||
|
||||
def set_pyenv_cfg(self) -> None:
|
||||
super().set_pyenv_cfg()
|
||||
self.pyenv_cfg["include-system-site-packages"] = "true" if self.enable_system_site_package else "false"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ViaGlobalRefApi",
|
||||
"ViaGlobalRefMeta",
|
||||
]
|
||||
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.create.creator import Creator
|
||||
from virtualenv.create.describe import Describe
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
from virtualenv.config.cli.parser import VirtualEnvOptions
|
||||
|
||||
|
||||
class VirtualenvBuiltin(Creator, Describe, ABC):
|
||||
"""A creator that does operations itself without delegation, if we can create it we can also describe it."""
|
||||
|
||||
def __init__(self, options: VirtualEnvOptions, interpreter: PythonInfo) -> None:
|
||||
Creator.__init__(self, options, interpreter)
|
||||
Describe.__init__(self, self.dest, interpreter)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"VirtualenvBuiltin",
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from abc import ABC
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.create.describe import PosixSupports, WindowsSupports
|
||||
from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen
|
||||
from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
|
||||
class CPython(ViaGlobalRefVirtualenvBuiltin, ABC):
|
||||
@classmethod
|
||||
def can_describe(cls, interpreter: PythonInfo) -> bool:
|
||||
return interpreter.implementation == "CPython" and super().can_describe(interpreter)
|
||||
|
||||
@classmethod
|
||||
def exe_stem(cls) -> str:
|
||||
return "python"
|
||||
|
||||
|
||||
class CPythonPosix(CPython, PosixSupports, ABC):
|
||||
"""Create a CPython virtual environment on POSIX platforms."""
|
||||
|
||||
@classmethod
|
||||
def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], str, str]]:
|
||||
host_exe = Path(interpreter.system_executable) # ty: ignore[invalid-argument-type]
|
||||
minor = interpreter.version_info.minor
|
||||
names = [
|
||||
"python",
|
||||
"python3",
|
||||
f"python3.{minor}",
|
||||
*((f"python3.{minor}t",) if interpreter.free_threaded else ()),
|
||||
host_exe.name,
|
||||
]
|
||||
targets = OrderedDict((i, None) for i in names)
|
||||
yield host_exe, list(targets.keys()), RefMust.NA, RefWhen.ANY
|
||||
|
||||
|
||||
class CPythonWindows(CPython, WindowsSupports, ABC):
|
||||
@classmethod
|
||||
def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], str, str]]:
|
||||
# symlink of the python executables does not work reliably, copy always instead
|
||||
# - https://bugs.python.org/issue42013
|
||||
# - venv
|
||||
host = cls.host_python(interpreter)
|
||||
minor = interpreter.version_info.minor
|
||||
names = {
|
||||
"python.exe",
|
||||
"python3.exe",
|
||||
"python3",
|
||||
host.name,
|
||||
*((f"python3.{minor}t.exe",) if interpreter.free_threaded else ()),
|
||||
}
|
||||
for path in (host.parent / n for n in names):
|
||||
yield host, [path.name], RefMust.COPY, RefWhen.ANY
|
||||
# for more info on pythonw.exe see https://stackoverflow.com/a/30313091
|
||||
python_w = host.parent / "pythonw.exe"
|
||||
yield (
|
||||
python_w,
|
||||
[python_w.name, "pythonw3.exe", *((f"pythonw3.{minor}t.exe",) if interpreter.free_threaded else ())],
|
||||
RefMust.COPY,
|
||||
RefWhen.ANY,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def host_python(cls, interpreter: PythonInfo) -> Path:
|
||||
return Path(interpreter.system_executable) # ty: ignore[invalid-argument-type]
|
||||
|
||||
|
||||
def is_mac_os_framework(interpreter: PythonInfo) -> bool:
|
||||
if interpreter.platform == "darwin":
|
||||
return interpreter.sysconfig_vars.get("PYTHONFRAMEWORK") == "Python3"
|
||||
return False
|
||||
|
||||
|
||||
def is_macos_brew(interpreter: PythonInfo) -> bool:
|
||||
return interpreter.platform == "darwin" and _BREW.fullmatch(interpreter.system_prefix) is not None
|
||||
|
||||
|
||||
_BREW = re.compile(
|
||||
r"/(usr/local|opt/homebrew)/(opt/python@3\.\d{1,2}|Cellar/python@3\.\d{1,2}/3\.\d{1,2}\.\d{1,2})/Frameworks/"
|
||||
r"Python\.framework/Versions/3\.\d{1,2}",
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CPython",
|
||||
"CPythonPosix",
|
||||
"CPythonWindows",
|
||||
"is_mac_os_framework",
|
||||
"is_macos_brew",
|
||||
]
|
||||
vendor/python-windows/Lib/site-packages/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import fnmatch
|
||||
from operator import methodcaller as method
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.create.describe import Python3Supports
|
||||
from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest, RefWhen
|
||||
from virtualenv.create.via_global_ref.store import is_store_python
|
||||
from virtualenv.util.path import copy as copy_path
|
||||
from virtualenv.util.path import ensure_dir
|
||||
|
||||
from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework, is_macos_brew
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
from virtualenv.create.via_global_ref.builtin.ref import PathRef
|
||||
from virtualenv.create.via_global_ref.builtin.via_global_self_do import BuiltinViaGlobalRefMeta
|
||||
from virtualenv.create.via_global_ref.venv import Venv
|
||||
|
||||
|
||||
class CPython3(CPython, Python3Supports, abc.ABC):
|
||||
"""CPython 3 or later."""
|
||||
|
||||
|
||||
class CPython3Posix(CPythonPosix, CPython3):
|
||||
@classmethod
|
||||
def can_describe(cls, interpreter: PythonInfo) -> bool:
|
||||
return (
|
||||
is_mac_os_framework(interpreter) is False
|
||||
and is_macos_brew(interpreter) is False
|
||||
and super().can_describe(interpreter)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]: # ty: ignore[invalid-method-override]
|
||||
yield from super().sources(interpreter)
|
||||
if shared_lib := cls._shared_libpython(interpreter):
|
||||
yield PathRefToDest(shared_lib, dest=cls._to_lib, when=RefWhen.COPY)
|
||||
|
||||
@classmethod
|
||||
def _to_lib(cls, creator: CPython3Posix, src: Path) -> Path:
|
||||
return creator.dest / "lib" / src.name
|
||||
|
||||
@classmethod
|
||||
def _shared_libpython(cls, interpreter: PythonInfo) -> Path | None:
|
||||
if not interpreter.sysconfig_vars.get("Py_ENABLE_SHARED"):
|
||||
return None
|
||||
if not (instsoname := interpreter.sysconfig_vars.get("INSTSONAME")):
|
||||
return None
|
||||
if not (libdir := interpreter.sysconfig_vars.get("LIBDIR")):
|
||||
return None
|
||||
if not (lib_path := Path(libdir) / instsoname).exists():
|
||||
return None
|
||||
return lib_path
|
||||
|
||||
def install_venv_shared_libs(self, venv_creator: Venv) -> None:
|
||||
if venv_creator.symlinks:
|
||||
return
|
||||
if not (shared_lib := self._shared_libpython(venv_creator.interpreter)):
|
||||
return
|
||||
dest = venv_creator.dest / "lib" / shared_lib.name
|
||||
ensure_dir(dest.parent)
|
||||
copy_path(shared_lib, dest)
|
||||
|
||||
|
||||
class CPython3Windows(CPythonWindows, CPython3):
|
||||
"""CPython 3 on Windows."""
|
||||
|
||||
@classmethod
|
||||
def setup_meta(cls, interpreter: PythonInfo) -> BuiltinViaGlobalRefMeta | None: # ty: ignore[invalid-method-override]
|
||||
if is_store_python(interpreter): # store python is not supported here
|
||||
return None
|
||||
return super().setup_meta(interpreter)
|
||||
|
||||
@classmethod
|
||||
def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]: # ty: ignore[invalid-method-override]
|
||||
yield from cls.executables(interpreter)
|
||||
if not cls.has_shim(interpreter):
|
||||
yield from cls.dll_and_pyd(interpreter)
|
||||
yield from cls.python_zip(interpreter)
|
||||
|
||||
@classmethod
|
||||
def _debug_suffix(cls, interpreter: PythonInfo) -> str:
|
||||
return "_d" if getattr(interpreter, "debug_build", False) else ""
|
||||
|
||||
@classmethod
|
||||
def executables(cls, interpreter: PythonInfo) -> list[PathRef] | Generator[PathRef]:
|
||||
sources = super().sources(interpreter)
|
||||
if interpreter.version_info >= (3, 13):
|
||||
host = cls.host_python(interpreter)
|
||||
t_suffix = "t" if interpreter.free_threaded else ""
|
||||
d_suffix = cls._debug_suffix(interpreter)
|
||||
updated_sources: list[PathRef] = []
|
||||
for ref in sources:
|
||||
if ref.base == "python.exe":
|
||||
launcher_path = host.with_name(f"venvlauncher{t_suffix}{d_suffix}.exe")
|
||||
if launcher_path.exists():
|
||||
new_ref = ExePathRefToDest(
|
||||
launcher_path, dest=ref.dest, targets=[ref.base, *ref.aliases], must=ref.must, when=ref.when
|
||||
)
|
||||
updated_sources.append(new_ref)
|
||||
continue
|
||||
elif ref.src.name == "pythonw.exe":
|
||||
w_launcher_path = ref.src.with_name(f"venvwlauncher{t_suffix}{d_suffix}.exe")
|
||||
if w_launcher_path.exists():
|
||||
new_ref = ExePathRefToDest(
|
||||
w_launcher_path,
|
||||
dest=ref.dest,
|
||||
targets=[ref.base, *ref.aliases],
|
||||
must=ref.must,
|
||||
when=ref.when,
|
||||
)
|
||||
updated_sources.append(new_ref)
|
||||
continue
|
||||
updated_sources.append(ref)
|
||||
return updated_sources
|
||||
return sources
|
||||
|
||||
@classmethod
|
||||
def has_shim(cls, interpreter: PythonInfo) -> bool:
|
||||
return cls.shim(interpreter) is not None
|
||||
|
||||
@classmethod
|
||||
def shim(cls, interpreter: PythonInfo) -> Path | None:
|
||||
root = Path(interpreter.system_stdlib) / "venv" / "scripts" / "nt"
|
||||
d_suffix = cls._debug_suffix(interpreter)
|
||||
if interpreter.version_info >= (3, 13):
|
||||
# https://github.com/python/cpython/issues/112984
|
||||
t_suffix = "t" if interpreter.free_threaded else ""
|
||||
exe_name = f"venvlauncher{t_suffix}{d_suffix}.exe"
|
||||
else:
|
||||
exe_name = f"python{d_suffix}.exe"
|
||||
if (shim := root / exe_name).exists():
|
||||
return shim
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def host_python(cls, interpreter: PythonInfo) -> Path:
|
||||
if cls.has_shim(interpreter):
|
||||
# starting with CPython 3.7 Windows ships with a venvlauncher.exe that avoids the need for dll/pyd copies
|
||||
# it also means the wrapper must be copied to avoid bugs such as https://bugs.python.org/issue42013
|
||||
return cls.shim(interpreter) # ty: ignore[invalid-return-type]
|
||||
return super().host_python(interpreter)
|
||||
|
||||
@classmethod
|
||||
def dll_and_pyd(cls, interpreter: PythonInfo) -> Generator[PathRefToDest]:
|
||||
folders = [Path(interpreter.system_executable).parent] # ty: ignore[invalid-argument-type]
|
||||
|
||||
# May be missing on some Python hosts.
|
||||
# See https://github.com/pypa/virtualenv/issues/2368
|
||||
dll_folder = Path(interpreter.system_prefix) / "DLLs"
|
||||
if dll_folder.is_dir():
|
||||
folders.append(dll_folder)
|
||||
|
||||
for folder in folders:
|
||||
for file in folder.iterdir():
|
||||
if file.suffix in {".pyd", ".dll"}:
|
||||
# Skip pywin32 DLLs to avoid conflicts with pywin32 installation
|
||||
# pywin32 has its own post-install that places DLLs in site-packages/pywin32_system32
|
||||
# See https://github.com/pypa/virtualenv/issues/2662
|
||||
if cls._is_pywin32_dll(file.name):
|
||||
continue
|
||||
yield PathRefToDest(file, cls.to_bin)
|
||||
|
||||
@classmethod
|
||||
def _is_pywin32_dll(cls, filename: str) -> bool:
|
||||
"""Check if a DLL file belongs to pywin32."""
|
||||
# pywin32 DLLs follow patterns like: pywintypes39.dll, pythoncom39.dll
|
||||
name_lower = filename.lower()
|
||||
return name_lower.startswith(("pywintypes", "pythoncom"))
|
||||
|
||||
@classmethod
|
||||
def python_zip(cls, interpreter: PythonInfo) -> Generator[PathRefToDest]:
|
||||
"""``python{VERSION}.zip`` contains compiled ``*.pyc`` std lib packages, where ``VERSION`` is ``py_version_nodot`` var from the ``sysconfig`` module.
|
||||
|
||||
See https://docs.python.org/3/using/windows.html#the-embeddable-package, ``discovery.py_info.PythonInfo`` class
|
||||
(interpreter), and ``python -m sysconfig`` output.
|
||||
|
||||
The embeddable Python distribution for Windows includes ``python{VERSION}.zip`` and ``python{VERSION}._pth``
|
||||
files. User can move/rename the zip file and edit ``sys.path`` by editing the ``_pth`` file. Here the
|
||||
``pattern`` is used only for the default zip file name.
|
||||
|
||||
"""
|
||||
pattern = f"*python{interpreter.version_nodot}.zip"
|
||||
matches = fnmatch.filter(interpreter.path, pattern)
|
||||
matched_paths = map(Path, matches)
|
||||
existing_paths = filter(method("exists"), matched_paths)
|
||||
if (path := next(existing_paths, None)) is not None:
|
||||
yield PathRefToDest(path, cls.to_bin)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CPython3",
|
||||
"CPython3Posix",
|
||||
"CPython3Windows",
|
||||
]
|
||||
Vendored
+290
@@ -0,0 +1,290 @@
|
||||
"""The Apple Framework builds require their own customization."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.create.via_global_ref.builtin.ref import (
|
||||
ExePathRefToDest,
|
||||
PathRefToDest,
|
||||
RefMust,
|
||||
)
|
||||
from virtualenv.create.via_global_ref.builtin.via_global_self_do import BuiltinViaGlobalRefMeta
|
||||
|
||||
from .common import CPython, CPythonPosix, is_mac_os_framework, is_macos_brew
|
||||
from .cpython3 import CPython3
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Generator
|
||||
from io import BufferedRandom
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
from virtualenv.create.via_global_ref.builtin.ref import PathRef
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CPythonmacOsFramework(CPython, ABC):
|
||||
@classmethod
|
||||
def can_describe(cls, interpreter: PythonInfo) -> bool:
|
||||
return is_mac_os_framework(interpreter) and super().can_describe(interpreter)
|
||||
|
||||
def create(self) -> None:
|
||||
super().create()
|
||||
|
||||
target = self.desired_mach_o_image_path()
|
||||
current = self.current_mach_o_image_path()
|
||||
for src in self._sources:
|
||||
if isinstance(src, ExePathRefToDest) and (src.must == RefMust.COPY or not self.symlinks):
|
||||
exes = [self.bin_dir / src.base]
|
||||
if not self.symlinks:
|
||||
exes.extend(self.bin_dir / a for a in src.aliases)
|
||||
for exe in exes:
|
||||
fix_mach_o(str(exe), current, target, self.interpreter.max_size) # ty: ignore[invalid-argument-type]
|
||||
try:
|
||||
subprocess.check_call(["codesign", "--force", "--sign", "-", str(exe)]) # noqa: S607
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
LOGGER.warning("Could not ad-hoc re-sign %s: %s", exe, e)
|
||||
|
||||
@classmethod
|
||||
def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], str, str]]:
|
||||
for _, targets, must, when in super()._executables(interpreter):
|
||||
# Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the
|
||||
# stub executable in ${sys.prefix}/bin.
|
||||
# See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951
|
||||
fixed_host_exe = Path(interpreter.prefix) / "Resources" / "Python.app" / "Contents" / "MacOS" / "Python" # ty: ignore[invalid-argument-type]
|
||||
yield fixed_host_exe, targets, must, when
|
||||
|
||||
@abstractmethod
|
||||
def current_mach_o_image_path(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def desired_mach_o_image_path(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix):
|
||||
def current_mach_o_image_path(self) -> str:
|
||||
return "@executable_path/../../../../Python3"
|
||||
|
||||
def desired_mach_o_image_path(self) -> str:
|
||||
return "@executable_path/../.Python"
|
||||
|
||||
@classmethod
|
||||
def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]: # ty: ignore[invalid-method-override]
|
||||
yield from super().sources(interpreter)
|
||||
|
||||
# add a symlink to the host python image
|
||||
exe = Path(interpreter.prefix) / "Python3" # ty: ignore[invalid-argument-type]
|
||||
yield PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK)
|
||||
|
||||
@property
|
||||
def reload_code(self) -> str:
|
||||
result = super().reload_code # ty: ignore[unresolved-attribute]
|
||||
return dedent(
|
||||
f"""
|
||||
# the bundled site.py always adds the global site package if we're on python framework build, escape this
|
||||
import sys
|
||||
before = sys._framework
|
||||
try:
|
||||
sys._framework = None
|
||||
{result}
|
||||
finally:
|
||||
sys._framework = before
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def fix_mach_o(exe: str, current: str, new: str, max_size: int) -> None:
|
||||
"""https://en.wikipedia.org/wiki/Mach-O.
|
||||
|
||||
Mach-O, short for Mach object file format, is a file format for executables, object code, shared libraries,
|
||||
dynamically-loaded code, and core dumps. A replacement for the a.out format, Mach-O offers more extensibility and
|
||||
faster access to information in the symbol table.
|
||||
|
||||
Each Mach-O file is made up of one Mach-O header, followed by a series of load commands, followed by one or more
|
||||
segments, each of which contains between 0 and 255 sections. Mach-O uses the REL relocation format to handle
|
||||
references to symbols. When looking up symbols Mach-O uses a two-level namespace that encodes each symbol into an
|
||||
'object/symbol name' pair that is then linearly searched for by first the object and then the symbol name.
|
||||
|
||||
The basic structure—a list of variable-length "load commands" that reference pages of data elsewhere in the file—was
|
||||
also used in the executable file format for Accent. The Accent file format was in turn, based on an idea from Spice
|
||||
Lisp.
|
||||
|
||||
With the introduction of Mac OS X 10.6 platform the Mach-O file underwent a significant modification that causes
|
||||
binaries compiled on a computer running 10.6 or later to be (by default) executable only on computers running Mac OS
|
||||
X 10.6 or later. The difference stems from load commands that the dynamic linker, in previous Mac OS X versions,
|
||||
does not understand. Another significant change to the Mach-O format is the change in how the Link Edit tables
|
||||
(found in the __LINKEDIT section) function. In 10.6 these new Link Edit tables are compressed by removing unused and
|
||||
unneeded bits of information, however Mac OS X 10.5 and earlier cannot read this new Link Edit table format.
|
||||
|
||||
"""
|
||||
try:
|
||||
LOGGER.debug("change Mach-O for %s from %s to %s", exe, current, new)
|
||||
_builtin_change_mach_o(max_size)(exe, current, new)
|
||||
except Exception as e: # noqa: BLE001
|
||||
LOGGER.warning("Could not call _builtin_change_mac_o: %s. Trying to call install_name_tool instead.", e)
|
||||
try:
|
||||
cmd = ["install_name_tool", "-change", current, new, exe]
|
||||
subprocess.check_call(cmd)
|
||||
except Exception:
|
||||
logging.fatal("Could not call install_name_tool -- you must have Apple's development tools installed")
|
||||
raise
|
||||
|
||||
|
||||
def _builtin_change_mach_o(maxint: int) -> Callable[[str, str, str], None]: # noqa: C901
|
||||
MH_MAGIC = 0xFEEDFACE # noqa: N806
|
||||
MH_CIGAM = 0xCEFAEDFE # noqa: N806
|
||||
MH_MAGIC_64 = 0xFEEDFACF # noqa: N806
|
||||
MH_CIGAM_64 = 0xCFFAEDFE # noqa: N806
|
||||
FAT_MAGIC = 0xCAFEBABE # noqa: N806
|
||||
BIG_ENDIAN = ">" # noqa: N806
|
||||
LITTLE_ENDIAN = "<" # noqa: N806
|
||||
LC_LOAD_DYLIB = 0xC # noqa: N806
|
||||
|
||||
class FileView:
|
||||
"""A proxy for file-like objects that exposes a given view of a file. Modified from macholib."""
|
||||
|
||||
def __init__(self, file_obj: FileView | BufferedRandom, start: int = 0, size: int = maxint) -> None:
|
||||
if isinstance(file_obj, FileView):
|
||||
self._file_obj = file_obj._file_obj # noqa: SLF001
|
||||
else:
|
||||
self._file_obj = file_obj
|
||||
self._start = start
|
||||
self._end = start + size
|
||||
self._pos = 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<fileview [{self._start:d}, {self._end:d}] {self._file_obj!r}>"
|
||||
|
||||
def tell(self) -> int:
|
||||
return self._pos
|
||||
|
||||
def _checkwindow(self, seek_to: int, op: str) -> None:
|
||||
if not (self._start <= seek_to <= self._end):
|
||||
msg = f"{op} to offset {seek_to:d} is outside window [{self._start:d}, {self._end:d}]"
|
||||
raise OSError(msg)
|
||||
|
||||
def seek(self, offset: int, whence: int = 0) -> None:
|
||||
seek_to = offset
|
||||
if whence == os.SEEK_SET:
|
||||
seek_to += self._start
|
||||
elif whence == os.SEEK_CUR:
|
||||
seek_to += self._start + self._pos
|
||||
elif whence == os.SEEK_END:
|
||||
seek_to += self._end
|
||||
else:
|
||||
msg = f"Invalid whence argument to seek: {whence!r}"
|
||||
raise OSError(msg)
|
||||
self._checkwindow(seek_to, "seek")
|
||||
self._file_obj.seek(seek_to)
|
||||
self._pos = seek_to - self._start
|
||||
|
||||
def write(self, content: bytes) -> None:
|
||||
here = self._start + self._pos
|
||||
self._checkwindow(here, "write")
|
||||
self._checkwindow(here + len(content), "write")
|
||||
self._file_obj.seek(here, os.SEEK_SET)
|
||||
self._file_obj.write(content)
|
||||
self._pos += len(content)
|
||||
|
||||
def read(self, size: int = maxint) -> bytes:
|
||||
assert size >= 0 # noqa: S101
|
||||
here = self._start + self._pos
|
||||
self._checkwindow(here, "read")
|
||||
size = min(size, self._end - here)
|
||||
self._file_obj.seek(here, os.SEEK_SET)
|
||||
read_bytes = self._file_obj.read(size)
|
||||
self._pos += len(read_bytes)
|
||||
return read_bytes
|
||||
|
||||
def read_data(file: FileView, endian: str, num: int = 1) -> int | tuple[int, ...]:
|
||||
"""Read a given number of 32-bits unsigned integers from the given file with the given endianness."""
|
||||
res = struct.unpack(endian + "L" * num, file.read(num * 4))
|
||||
if len(res) == 1:
|
||||
return res[0]
|
||||
return res
|
||||
|
||||
def mach_o_change(at_path: str, what: str, value: str) -> None: # noqa: C901
|
||||
"""Replace a given name (what) in any LC_LOAD_DYLIB command found in the given binary with a new name (value), provided it's shorter."""
|
||||
|
||||
def do_macho(file: FileView, bits: int, endian: str) -> None:
|
||||
# Read Mach-O header (the magic number is assumed read by the caller)
|
||||
_cpu_type, _cpu_sub_type, _file_type, n_commands, _size_of_commands, _flags = read_data(file, endian, 6) # ty: ignore[not-iterable]
|
||||
# 64-bits header has one more field.
|
||||
if bits == 64: # noqa: PLR2004
|
||||
read_data(file, endian)
|
||||
# The header is followed by n commands
|
||||
for _ in range(n_commands):
|
||||
where = file.tell()
|
||||
# Read command header
|
||||
cmd, cmd_size = read_data(file, endian, 2) # ty: ignore[not-iterable]
|
||||
if cmd == LC_LOAD_DYLIB:
|
||||
# The first data field in LC_LOAD_DYLIB commands is the offset of the name, starting from the
|
||||
# beginning of the command.
|
||||
name_offset = read_data(file, endian)
|
||||
file.seek(where + name_offset, os.SEEK_SET) # ty: ignore[unsupported-operator]
|
||||
# Read the NUL terminated string
|
||||
load = file.read(cmd_size - name_offset).decode() # ty: ignore[unsupported-operator]
|
||||
load = load[: load.index("\0")]
|
||||
# If the string is what is being replaced, overwrite it.
|
||||
if load == what:
|
||||
file.seek(where + name_offset, os.SEEK_SET) # ty: ignore[unsupported-operator]
|
||||
file.write(value.encode() + b"\0")
|
||||
# Seek to the next command
|
||||
file.seek(where + cmd_size, os.SEEK_SET)
|
||||
|
||||
def do_file(file: FileView | BufferedRandom, offset: int = 0, size: int = maxint) -> None:
|
||||
file = FileView(file, offset, size)
|
||||
# Read magic number
|
||||
magic = read_data(file, BIG_ENDIAN)
|
||||
if magic == FAT_MAGIC:
|
||||
# Fat binaries contain nfat_arch Mach-O binaries
|
||||
n_fat_arch = read_data(file, BIG_ENDIAN)
|
||||
for _ in range(n_fat_arch): # ty: ignore[invalid-argument-type]
|
||||
# Read arch header
|
||||
_cpu_type, _cpu_sub_type, offset, size, _align = read_data(file, BIG_ENDIAN, 5) # ty: ignore[not-iterable]
|
||||
do_file(file, offset, size)
|
||||
elif magic == MH_MAGIC:
|
||||
do_macho(file, 32, BIG_ENDIAN)
|
||||
elif magic == MH_CIGAM:
|
||||
do_macho(file, 32, LITTLE_ENDIAN)
|
||||
elif magic == MH_MAGIC_64:
|
||||
do_macho(file, 64, BIG_ENDIAN)
|
||||
elif magic == MH_CIGAM_64:
|
||||
do_macho(file, 64, LITTLE_ENDIAN)
|
||||
|
||||
assert len(what) >= len(value) # noqa: S101
|
||||
|
||||
with open(at_path, "r+b") as f:
|
||||
do_file(f)
|
||||
|
||||
return mach_o_change
|
||||
|
||||
|
||||
class CPython3macOsBrew(CPython3, CPythonPosix):
|
||||
@classmethod
|
||||
def can_describe(cls, interpreter: PythonInfo) -> bool:
|
||||
return is_macos_brew(interpreter) and super().can_describe(interpreter)
|
||||
|
||||
@classmethod
|
||||
def setup_meta(cls, interpreter: PythonInfo) -> BuiltinViaGlobalRefMeta: # noqa: ARG003
|
||||
meta = BuiltinViaGlobalRefMeta()
|
||||
meta.copy_error = "Brew disables copy creation: https://github.com/Homebrew/homebrew-core/issues/138159"
|
||||
return meta
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CPython3macOsBrew",
|
||||
"CPython3macOsFramework",
|
||||
"CPythonmacOsFramework",
|
||||
]
|
||||
vendor/python-windows/Lib/site-packages/virtualenv/create/via_global_ref/builtin/graalpy/__init__.py
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from virtualenv.create.describe import PosixSupports, WindowsSupports
|
||||
from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen
|
||||
from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator, Iterator
|
||||
|
||||
from python_discovery import PythonInfo
|
||||
|
||||
|
||||
class GraalPy(ViaGlobalRefVirtualenvBuiltin, ABC):
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def _native_lib(cls, lib_dir: Path, platform: str) -> Path:
|
||||
"""Return the path to the native library for this platform."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def can_describe(cls, interpreter: PythonInfo) -> bool:
|
||||
return interpreter.implementation == "GraalPy" and super().can_describe(interpreter)
|
||||
|
||||
@classmethod
|
||||
def exe_stem(cls) -> str:
|
||||
return "graalpy"
|
||||
|
||||
@classmethod
|
||||
def exe_names(cls, interpreter: PythonInfo) -> set[str]:
|
||||
return {
|
||||
cls.exe_stem(),
|
||||
"python",
|
||||
f"python{interpreter.version_info.major}",
|
||||
f"python{interpreter.version_info.major}.{interpreter.version_info.minor}",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _executables(cls, interpreter: PythonInfo) -> Generator[tuple[Path, list[str], RefMust, RefWhen], None, None]: # ty: ignore[invalid-method-override]
|
||||
host = Path(interpreter.system_executable) # ty: ignore[invalid-argument-type]
|
||||
targets = sorted(f"{name}{cls.suffix}" for name in cls.exe_names(interpreter))
|
||||
yield host, targets, RefMust.NA, RefWhen.ANY
|
||||
|
||||
@classmethod
|
||||
def sources(cls, interpreter: PythonInfo) -> Generator[PathRefToDest]: # ty: ignore[invalid-method-override]
|
||||
yield from super().sources(interpreter)
|
||||
python_dir = Path(interpreter.system_executable).resolve().parent # ty: ignore[invalid-argument-type]
|
||||
if python_dir.name in {"bin", "Scripts"}:
|
||||
python_dir = python_dir.parent
|
||||
|
||||
native_lib = cls._native_lib(python_dir / "lib", interpreter.platform)
|
||||
if native_lib.exists():
|
||||
yield PathRefToDest(native_lib, dest=lambda self, s: self.bin_dir.parent / "lib" / s.name)
|
||||
|
||||
for jvm_dir_name in ("jvm", "jvmlibs", "modules"):
|
||||
jvm_dir = python_dir / jvm_dir_name
|
||||
if jvm_dir.exists():
|
||||
yield PathRefToDest(jvm_dir, dest=lambda self, s: self.bin_dir.parent / s.name)
|
||||
|
||||
@classmethod
|
||||
def _shared_libs(cls, python_dir: Path) -> Iterator[Path]:
|
||||
raise NotImplementedError
|
||||
|
||||
def set_pyenv_cfg(self) -> None:
|
||||
super().set_pyenv_cfg()
|
||||
# GraalPy 24.0 and older had home without the bin
|
||||
version = self.interpreter.version_info
|
||||
if version.minor <= 10: # noqa: PLR2004
|
||||
home = Path(self.pyenv_cfg["home"])
|
||||
if home.name == "bin":
|
||||
self.pyenv_cfg["home"] = str(home.parent)
|
||||
|
||||
|
||||
class GraalPyPosix(GraalPy, PosixSupports):
|
||||
@classmethod
|
||||
def _native_lib(cls, lib_dir: Path, platform: str) -> Path:
|
||||
if platform == "darwin":
|
||||
return lib_dir / "libpythonvm.dylib"
|
||||
return lib_dir / "libpythonvm.so"
|
||||
|
||||
|
||||
class GraalPyWindows(GraalPy, WindowsSupports):
|
||||
@classmethod
|
||||
def _native_lib(cls, lib_dir: Path, platform: str) -> Path: # noqa: ARG003
|
||||
return lib_dir / "pythonvm.dll"
|
||||
|
||||
def set_pyenv_cfg(self) -> None:
|
||||
# GraalPy needs an additional entry in pyvenv.cfg on Windows
|
||||
super().set_pyenv_cfg()
|
||||
self.pyenv_cfg["venvlauncher_command"] = self.interpreter.system_executable # ty: ignore[invalid-assignment]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"GraalPyPosix",
|
||||
"GraalPyWindows",
|
||||
]
|
||||
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user