This commit is contained in:
Waylon Walker 2022-03-31 20:20:07 -05:00
commit 38355d2442
No known key found for this signature in database
GPG key ID: 66E2BF2B4190EFE4
9083 changed files with 1225834 additions and 0 deletions

View file

@ -0,0 +1,10 @@
from __future__ import absolute_import, unicode_literals
from .run import cli_run, session_via_cli
from .version import __version__
__all__ = (
"__version__",
"cli_run",
"session_via_cli",
)

View file

@ -0,0 +1,80 @@
from __future__ import absolute_import, print_function, unicode_literals
import logging
import os
import sys
from datetime import datetime
def run(args=None, options=None, env=None):
env = os.environ if env is None else env
start = datetime.now()
from virtualenv.run import cli_run
from virtualenv.util.error import ProcessCallFailed
if args is None:
args = sys.argv[1:]
try:
session = cli_run(args, options, env)
logging.warning(LogSession(session, start))
except ProcessCallFailed as exception:
print("subprocess call failed for {} with code {}".format(exception.cmd, exception.code))
print(exception.out, file=sys.stdout, end="")
print(exception.err, file=sys.stderr, end="")
raise SystemExit(exception.code)
class LogSession(object):
def __init__(self, session, start):
self.session = session
self.start = start
def __str__(self):
from virtualenv.util.six import ensure_text
spec = self.session.creator.interpreter.spec
elapsed = (datetime.now() - self.start).total_seconds() * 1000
lines = [
"created virtual environment {} in {:.0f}ms".format(spec, elapsed),
" creator {}".format(ensure_text(str(self.session.creator))),
]
if self.session.seeder.enabled:
lines += (
" seeder {}".format(ensure_text(str(self.session.seeder))),
" added seed packages: {}".format(
", ".join(
sorted(
"==".join(i.stem.split("-"))
for i in self.session.creator.purelib.iterdir()
if i.suffix == ".dist-info"
),
),
),
)
if self.session.activators:
lines.append(" activators {}".format(",".join(i.__class__.__name__ for i in self.session.activators)))
return "\n".join(lines)
def run_with_catch(args=None, env=None):
from virtualenv.config.cli.parser import VirtualEnvOptions
env = os.environ if env is None else env
options = VirtualEnvOptions()
try:
run(args, options, env)
except (KeyboardInterrupt, SystemExit, Exception) as exception:
try:
if getattr(options, "with_traceback", False):
raise
else:
if not (isinstance(exception, SystemExit) and exception.code == 0):
logging.error("%s: %s", type(exception).__name__, exception)
code = exception.code if isinstance(exception, SystemExit) else 1
sys.exit(code)
finally:
logging.shutdown() # force flush of log messages before the trace is printed
if __name__ == "__main__": # pragma: no cov
run_with_catch() # pragma: no cov

View file

@ -0,0 +1,19 @@
from __future__ import absolute_import, unicode_literals
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
__all__ = [
"BashActivator",
"PowerShellActivator",
"CShellActivator",
"PythonActivator",
"BatchActivator",
"FishActivator",
"NushellActivator",
]

View file

@ -0,0 +1,45 @@
from __future__ import absolute_import, unicode_literals
import os
from abc import ABCMeta, abstractmethod
from six import add_metaclass
@add_metaclass(ABCMeta)
class Activator(object):
"""Generates an activate script for the virtual environment"""
def __init__(self, options):
"""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):
"""Check if the activation script is supported in the given interpreter.
:param interpreter: the interpreter we need to support
:return: ``True`` if supported, ``False`` otherwise
"""
return True
@classmethod
def add_parser_arguments(cls, parser, interpreter):
"""
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):
"""Generate the 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

View file

@ -0,0 +1,13 @@
from __future__ import absolute_import, unicode_literals
from virtualenv.util.path import Path
from ..via_template import ViaTemplateActivator
class BashActivator(ViaTemplateActivator):
def templates(self):
yield Path("activate.sh")
def as_name(self, template):
return template.stem

View file

@ -0,0 +1,83 @@
# 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
# ! [ -z ${VAR+_} ] returns true if VAR is declared at all
if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
PATH="$_OLD_VIRTUAL_PATH"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
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
if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
PS1="$_OLD_VIRTUAL_PS1"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
if [ ! "${1-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV='__VIRTUAL_ENV__'
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
fi
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/__BIN_NAME__:$PATH"
export PATH
# unset PYTHONHOME if set
if ! [ -z "${PYTHONHOME+_}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1-}"
if [ "x__VIRTUAL_PROMPT__" != x ] ; then
PS1="(__VIRTUAL_PROMPT__) ${PS1-}"
else
PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}"
fi
export 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

View file

@ -0,0 +1,23 @@
from __future__ import absolute_import, unicode_literals
import os
from virtualenv.util.path import Path
from ..via_template import ViaTemplateActivator
class BatchActivator(ViaTemplateActivator):
@classmethod
def supports(cls, interpreter):
return interpreter.os == "nt"
def templates(self):
yield Path("activate.bat")
yield Path("deactivate.bat")
yield Path("pydoc.bat")
def instantiate_template(self, replacements, template, creator):
# ensure the text has all newlines as \r\n - required by batch
base = super(BatchActivator, self).instantiate_template(replacements, template, creator)
return base.replace(os.linesep, "\n").replace("\n", os.linesep)

View file

@ -0,0 +1,39 @@
@echo off
set "VIRTUAL_ENV=__VIRTUAL_ENV__"
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 (
if "__VIRTUAL_PROMPT__" NEQ "" (
set "PROMPT=(__VIRTUAL_PROMPT__) %PROMPT%"
) else (
for %%d in ("%VIRTUAL_ENV%") do set "PROMPT=(%%~nxd) %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=
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%"

View file

@ -0,0 +1,19 @@
@echo off
set VIRTUAL_ENV=
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 not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH
set "PATH=%_OLD_VIRTUAL_PATH%"
set _OLD_VIRTUAL_PATH=
:ENDIFVPATH

View file

@ -0,0 +1 @@
python.exe -m pydoc %*

View file

@ -0,0 +1,14 @@
from __future__ import absolute_import, unicode_literals
from virtualenv.util.path import Path
from ..via_template import ViaTemplateActivator
class CShellActivator(ViaTemplateActivator):
@classmethod
def supports(cls, interpreter):
return interpreter.os != "nt"
def templates(self):
yield Path("activate.csh")

View file

@ -0,0 +1,55 @@
# 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_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; 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 ('__VIRTUAL_PROMPT__' != "") then
set env_name = '(__VIRTUAL_PROMPT__) '
else
set env_name = '('"$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 = "$env_name:q$prompt:q"
endif
endif
endif
unset env_name
unset do_prompt
alias pydoc python -m pydoc
rehash

View file

@ -0,0 +1,10 @@
from __future__ import absolute_import, unicode_literals
from virtualenv.util.path import Path
from ..via_template import ViaTemplateActivator
class FishActivator(ViaTemplateActivator):
def templates(self):
yield Path("activate.fish")

View file

@ -0,0 +1,100 @@
# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*.
# Do not run it directly.
function _bashify_path -d "Converts a fish path to something bash can recognize"
set fishy_path $argv
set bashy_path $fishy_path[1]
for path_part in $fishy_path[2..-1]
set bashy_path "$bashy_path:$path_part"
end
echo $bashy_path
end
function _fishify_path -d "Converts a bash path to something fish can recognize"
echo $argv | tr ':' '\n'
end
function deactivate -d 'Exit virtualenv mode and return to the normal environment.'
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
if test (echo $FISH_VERSION | head -c 1) -lt 3
set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH")
else
set -gx PATH $_OLD_VIRTUAL_PATH
end
set -e _OLD_VIRTUAL_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 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.
functions -e fish_prompt
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
set -e _OLD_FISH_PROMPT_OVERRIDE
end
set -e VIRTUAL_ENV
if test "$argv[1]" != 'nondestructive'
# Self-destruct!
functions -e pydoc
functions -e deactivate
functions -e _bashify_path
functions -e _fishify_path
end
end
# Unset irrelevant variables.
deactivate nondestructive
set -gx VIRTUAL_ENV '__VIRTUAL_ENV__'
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
if test (echo $FISH_VERSION | head -c 1) -lt 3
set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH)
else
set -gx _OLD_VIRTUAL_PATH $PATH
end
set -gx PATH "$VIRTUAL_ENV"'/__BIN_NAME__' $PATH
# 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`.
functions -c fish_prompt _old_fish_prompt
function fish_prompt
# Run the user's prompt first; it might depend on (pipe)status.
set -l prompt (_old_fish_prompt)
# Prompt override provided?
# If not, just prepend the environment name.
if test -n '__VIRTUAL_PROMPT__'
printf '(%s) ' '__VIRTUAL_PROMPT__'
else
printf '(%s) ' (basename "$VIRTUAL_ENV")
end
string join -- \n $prompt # handle multi-line prompts
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
end

View file

@ -0,0 +1,28 @@
from __future__ import absolute_import, unicode_literals
import os
from virtualenv.util.path import Path
from virtualenv.util.six import ensure_text
from ..via_template import ViaTemplateActivator
class NushellActivator(ViaTemplateActivator):
def templates(self):
yield Path("activate.nu")
yield Path("deactivate.nu")
def replacements(self, creator, dest_folder):
# Due to nushell scoping, it isn't easy to create a function that will
# deactivate the environment. For that reason a __DEACTIVATE_PATH__
# replacement pointing to the deactivate.nu file is created
return {
"__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
"__VIRTUAL_ENV__": ensure_text(str(creator.dest)),
"__VIRTUAL_NAME__": creator.env_name,
"__BIN_NAME__": ensure_text(str(creator.bin_dir.relative_to(creator.dest))),
"__PATH_SEP__": ensure_text(os.pathsep),
"__DEACTIVATE_PATH__": ensure_text(str(Path(dest_folder) / "deactivate.nu")),
}

View file

@ -0,0 +1,92 @@
# This command prepares the required environment variables
def-env activate-virtualenv [] {
def is-string [x] {
($x | describe) == 'string'
}
def has-env [name: string] {
$name in (env).name
}
let is-windows = ((sys).host.name | str downcase) == 'windows'
let virtual-env = '__VIRTUAL_ENV__'
let bin = '__BIN_NAME__'
let path-sep = '__PATH_SEP__'
let path-name = if $is-windows {
if (has-env 'Path') {
'Path'
} else {
'PATH'
}
} else {
'PATH'
}
let old-path = (
if $is-windows {
if (has-env 'Path') {
$env.Path
} else {
$env.PATH
}
} else {
$env.PATH
} | if (is-string $in) {
# if Path/PATH is a string, make it a list
$in | split row $path-sep | path expand
} else {
$in
}
)
let venv-path = ([$virtual-env $bin] | path join)
let new-path = ($old-path | prepend $venv-path | str collect $path-sep)
# Creating the new prompt for the session
let virtual-prompt = if ('__VIRTUAL_PROMPT__' == '') {
$'(char lparen)($virtual-env | path basename)(char rparen) '
} else {
'(__VIRTUAL_PROMPT__) '
}
# Back up the old prompt builder
let old-prompt-command = if (has-env 'VIRTUAL_ENV') && (has-env '_OLD_PROMPT_COMMAND') {
$env._OLD_PROMPT_COMMAND
} else {
if (has-env 'PROMPT_COMMAND') {
$env.PROMPT_COMMAND
} else {
''
}
}
# If there is no default prompt, then only the env is printed in the prompt
let new-prompt = if (has-env 'PROMPT_COMMAND') {
if ($old-prompt-command | describe) == 'block' {
{ $'($virtual-prompt)(do $old-prompt-command)' }
} else {
{ $'($virtual-prompt)($old-prompt-command)' }
}
} else {
{ $'($virtual-prompt)' }
}
# Environment variables that will be batched loaded to the virtual env
let new-env = {
$path-name : $new-path
VIRTUAL_ENV : $virtual-env
_OLD_VIRTUAL_PATH : ($old-path | str collect $path-sep)
_OLD_PROMPT_COMMAND : $old-prompt-command
PROMPT_COMMAND : $new-prompt
VIRTUAL_PROMPT : $virtual-prompt
}
# Activate the environment variables
load-env $new-env
}
# Activate the virtualenv
activate-virtualenv
alias pydoc = python -m pydoc
alias deactivate = source '__DEACTIVATE_PATH__'

View file

@ -0,0 +1,32 @@
def-env deactivate-virtualenv [] {
def has-env [name: string] {
$name in (env).name
}
let is-windows = ((sys).host.name | str downcase) == 'windows'
let path-name = if $is-windows {
if (has-env 'Path') {
'Path'
} else {
'PATH'
}
} else {
'PATH'
}
load-env { $path-name : $env._OLD_VIRTUAL_PATH }
let-env PROMPT_COMMAND = $env._OLD_PROMPT_COMMAND
# Hiding the environment variables that were created when activating the env
hide _OLD_VIRTUAL_PATH
hide _OLD_PROMPT_COMMAND
hide VIRTUAL_ENV
hide VIRTUAL_PROMPT
}
deactivate-virtualenv
hide pydoc
hide deactivate

View file

@ -0,0 +1,10 @@
from __future__ import absolute_import, unicode_literals
from virtualenv.util.path import Path
from ..via_template import ViaTemplateActivator
class PowerShellActivator(ViaTemplateActivator):
def templates(self):
yield Path("activate.ps1")

View file

@ -0,0 +1,60 @@
$script:THIS_PATH = $myinvocation.mycommand.path
$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent
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 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 (!$NonDestructive) {
# Self destruct!
Remove-Item function:deactivate
Remove-Item function:pydoc
}
}
function global:pydoc {
python -m pydoc $args
}
# unset irrelevant variables
deactivate -nondestructive
$VIRTUAL_ENV = $BASE_DIR
$env:VIRTUAL_ENV = $VIRTUAL_ENV
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env: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
if ("__VIRTUAL_PROMPT__" -ne "") {
function global:prompt {
# Add the custom prefix to the existing prompt
$previous_prompt_value = & $function:_old_virtual_prompt
("(__VIRTUAL_PROMPT__) " + $previous_prompt_value)
}
}
else {
function global:prompt {
# Add a prefix to the current prompt, but don't discard it.
$previous_prompt_value = & $function:_old_virtual_prompt
$new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) "
($new_prompt_value + $previous_prompt_value)
}
}
}

View file

@ -0,0 +1,35 @@
from __future__ import absolute_import, unicode_literals
import os
import sys
from collections import OrderedDict
from virtualenv.util.path import Path
from virtualenv.util.six import ensure_text
from ..via_template import ViaTemplateActivator
class PythonActivator(ViaTemplateActivator):
def templates(self):
yield Path("activate_this.py")
def replacements(self, creator, dest_folder):
replacements = super(PythonActivator, self).replacements(creator, dest_folder)
lib_folders = OrderedDict((os.path.relpath(str(i), str(dest_folder)), None) for i in creator.libs)
win_py2 = creator.interpreter.platform == "win32" and creator.interpreter.version_info.major == 2
replacements.update(
{
"__LIB_FOLDERS__": ensure_text(os.pathsep.join(lib_folders.keys())),
"__DECODE_PATH__": ("yes" if win_py2 else ""),
},
)
return replacements
@staticmethod
def _repr_unicode(creator, value):
py2 = creator.interpreter.version_info.major == 2
if py2: # on Python 2 we need to encode this into explicit utf-8, py3 supports unicode literals
start = 2 if sys.version_info[0] == 3 else 1
value = ensure_text(repr(value.encode("utf-8"))[start:-1])
return value

View file

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
"""Activate virtualenv for current interpreter:
Use exec(open(this_file).read(), {'__file__': this_file}).
This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
"""
import os
import site
import sys
try:
abs_file = os.path.abspath(__file__)
except NameError:
raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))")
bin_dir = os.path.dirname(abs_file)
base = bin_dir[: -len("__BIN_NAME__") - 1] # strip away the bin part from the __file__, plus the path separator
# 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
# add the virtual environments libraries to the host python import mechanism
prev_length = len(sys.path)
for lib in "__LIB_FOLDERS__".split(os.pathsep):
path = os.path.realpath(os.path.join(bin_dir, lib))
site.addsitedir(path.decode("utf-8") if "__DECODE_PATH__" else path)
sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
sys.real_prefix = sys.prefix
sys.prefix = base

View file

@ -0,0 +1,67 @@
from __future__ import absolute_import, unicode_literals
import os
import sys
from abc import ABCMeta, abstractmethod
from six import add_metaclass
from virtualenv.util.six import ensure_text
from .activator import Activator
if sys.version_info >= (3, 7):
from importlib.resources import read_binary
else:
from importlib_resources import read_binary
@add_metaclass(ABCMeta)
class ViaTemplateActivator(Activator):
@abstractmethod
def templates(self):
raise NotImplementedError
def generate(self, creator):
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, dest_folder):
return {
"__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
"__VIRTUAL_ENV__": ensure_text(str(creator.dest)),
"__VIRTUAL_NAME__": creator.env_name,
"__BIN_NAME__": ensure_text(str(creator.bin_dir.relative_to(creator.dest))),
"__PATH_SEP__": ensure_text(os.pathsep),
}
def _generate(self, replacements, templates, to_folder, creator):
generated = []
for template in templates:
text = self.instantiate_template(replacements, template, creator)
dest = to_folder / self.as_name(template)
# use write_bytes to avoid platform specific line normalization (\n -> \r\n)
dest.write_bytes(text.encode("utf-8"))
generated.append(dest)
return generated
def as_name(self, template):
return template.name
def instantiate_template(self, replacements, template, creator):
# read content as binary to avoid platform specific line normalization (\n -> \r\n)
binary = read_binary(self.__module__, str(template))
text = binary.decode("utf-8", errors="strict")
for key, value in replacements.items():
value = self._repr_unicode(creator, value)
text = text.replace(key, value)
return text
@staticmethod
def _repr_unicode(creator, value):
# by default we just let it be unicode
return value

View file

@ -0,0 +1,58 @@
"""
Application data stored by virtualenv.
"""
from __future__ import absolute_import, unicode_literals
import logging
import os
from platformdirs import user_data_dir
from .na import AppDataDisabled
from .read_only import ReadOnlyAppData
from .via_disk_folder import AppDataDiskFolder
from .via_tempdir import TempAppData
def _default_app_data_dir(env):
key = str("VIRTUALENV_OVERRIDE_APP_DATA")
if key in env:
return env[key]
else:
return user_data_dir(appname="virtualenv", appauthor="pypa")
def make_app_data(folder, **kwargs):
read_only = kwargs.pop("read_only")
env = kwargs.pop("env")
if kwargs: # py3+ kwonly
raise TypeError("unexpected keywords: {}")
if folder is None:
folder = _default_app_data_dir(env)
folder = os.path.abspath(folder)
if read_only:
return ReadOnlyAppData(folder)
if not os.path.isdir(folder):
try:
os.makedirs(folder)
logging.debug("created app data folder %s", folder)
except OSError as exception:
logging.info("could not create app data folder %s due to %r", folder, exception)
if os.access(folder, os.W_OK):
return AppDataDiskFolder(folder)
else:
logging.debug("app data folder %s has no write access", folder)
return TempAppData()
__all__ = (
"AppDataDisabled",
"AppDataDiskFolder",
"ReadOnlyAppData",
"TempAppData",
"make_app_data",
)

View file

@ -0,0 +1,95 @@
"""
Application data stored by virtualenv.
"""
from __future__ import absolute_import, unicode_literals
from abc import ABCMeta, abstractmethod
from contextlib import contextmanager
import six
from virtualenv.info import IS_ZIPAPP
@six.add_metaclass(ABCMeta)
class AppData(object):
"""Abstract storage interface for the virtualenv application"""
@abstractmethod
def close(self):
"""called before virtualenv exits"""
@abstractmethod
def reset(self):
"""called when the user passes in the reset app data"""
@abstractmethod
def py_info(self, path):
raise NotImplementedError
@abstractmethod
def py_info_clear(self):
raise NotImplementedError
@property
def can_update(self):
raise NotImplementedError
@abstractmethod
def embed_update_log(self, distribution, for_py_version):
raise NotImplementedError
@property
def house(self):
raise NotImplementedError
@property
def transient(self):
raise NotImplementedError
@abstractmethod
def wheel_image(self, for_py_version, name):
raise NotImplementedError
@contextmanager
def ensure_extracted(self, path, to_folder=None):
"""Some paths might be within the zipapp, unzip these to a path on the disk"""
if IS_ZIPAPP:
with self.extract(path, to_folder) as result:
yield result
else:
yield path
@abstractmethod
@contextmanager
def extract(self, path, to_folder):
raise NotImplementedError
@abstractmethod
@contextmanager
def locked(self, path):
raise NotImplementedError
@six.add_metaclass(ABCMeta)
class ContentStore(object):
@abstractmethod
def exists(self):
raise NotImplementedError
@abstractmethod
def read(self):
raise NotImplementedError
@abstractmethod
def write(self, content):
raise NotImplementedError
@abstractmethod
def remove(self):
raise NotImplementedError
@abstractmethod
@contextmanager
def locked(self):
pass

View file

@ -0,0 +1,66 @@
from __future__ import absolute_import, unicode_literals
from contextlib import contextmanager
from .base import AppData, ContentStore
class AppDataDisabled(AppData):
"""No application cache available (most likely as we don't have write permissions)"""
transient = True
can_update = False
def __init__(self):
pass
error = RuntimeError("no app data folder available, probably no write access to the folder")
def close(self):
"""do nothing"""
def reset(self):
"""do nothing"""
def py_info(self, path):
return ContentStoreNA()
def embed_update_log(self, distribution, for_py_version):
return ContentStoreNA()
def extract(self, path, to_folder):
raise self.error
@contextmanager
def locked(self, path):
"""do nothing"""
yield
@property
def house(self):
raise self.error
def wheel_image(self, for_py_version, name):
raise self.error
def py_info_clear(self):
""" """
class ContentStoreNA(ContentStore):
def exists(self):
return False
def read(self):
""" """
return None
def write(self, content):
""" """
def remove(self):
""" """
@contextmanager
def locked(self):
yield

View file

@ -0,0 +1,34 @@
import os.path
from virtualenv.util.lock import NoOpFileLock
from .via_disk_folder import AppDataDiskFolder, PyInfoStoreDisk
class ReadOnlyAppData(AppDataDiskFolder):
can_update = False
def __init__(self, folder): # type: (str) -> None
if not os.path.isdir(folder):
raise RuntimeError("read-only app data directory {} does not exist".format(folder))
self.lock = NoOpFileLock(folder)
def reset(self): # type: () -> None
raise RuntimeError("read-only app data does not support reset")
def py_info_clear(self): # type: () -> None
raise NotImplementedError
def py_info(self, path):
return _PyInfoStoreDiskReadOnly(self.py_info_at, path)
def embed_update_log(self, distribution, for_py_version):
raise NotImplementedError
class _PyInfoStoreDiskReadOnly(PyInfoStoreDisk):
def write(self, content):
raise RuntimeError("read-only app data python info cannot be updated")
__all__ = ("ReadOnlyAppData",)

View file

@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
"""
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
"""
from __future__ import absolute_import, unicode_literals
import json
import logging
from abc import ABCMeta
from contextlib import contextmanager
from hashlib import sha256
import six
from virtualenv.util.lock import ReentrantFileLock
from virtualenv.util.path import safe_delete
from virtualenv.util.six import ensure_text
from virtualenv.util.zipapp import extract
from virtualenv.version import __version__
from .base import AppData, ContentStore
class AppDataDiskFolder(AppData):
"""
Store the application data on the disk within a folder layout.
"""
transient = False
can_update = True
def __init__(self, folder):
self.lock = ReentrantFileLock(folder)
def __repr__(self):
return "{}({})".format(type(self).__name__, self.lock.path)
def __str__(self):
return str(self.lock.path)
def reset(self):
logging.debug("reset app data folder %s", self.lock.path)
safe_delete(self.lock.path)
def close(self):
"""do nothing"""
@contextmanager
def locked(self, path):
path_lock = self.lock / path
with path_lock:
yield path_lock.path
@contextmanager
def extract(self, path, to_folder):
if to_folder is not None:
root = ReentrantFileLock(to_folder())
else:
root = self.lock / "unzip" / __version__
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):
return self.lock / "py_info" / "1"
def py_info(self, path):
return PyInfoStoreDisk(self.py_info_at, path)
def py_info_clear(self):
""" """
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, for_py_version):
return EmbedDistributionUpdateStoreDisk(self.lock / "wheel" / for_py_version / "embed" / "3", distribution)
@property
def house(self):
path = self.lock.path / "wheel" / "house"
path.mkdir(parents=True, exist_ok=True)
return path
def wheel_image(self, for_py_version, name):
return self.lock.path / "wheel" / for_py_version / "image" / "1" / name
@six.add_metaclass(ABCMeta)
class JSONStoreDisk(ContentStore):
def __init__(self, in_folder, key, msg, msg_args):
self.in_folder = in_folder
self.key = key
self.msg = msg
self.msg_args = msg_args + (self.file,)
@property
def file(self):
return self.in_folder.path / "{}.json".format(self.key)
def exists(self):
return self.file.exists()
def read(self):
data, bad_format = None, False
try:
data = json.loads(self.file.read_text())
logging.debug("got {} from %s".format(self.msg), *self.msg_args)
return data
except ValueError:
bad_format = True
except Exception: # noqa
pass
if bad_format:
try:
self.remove()
except OSError: # reading and writing on the same file may cause race on multiple processes
pass
return None
def remove(self):
self.file.unlink()
logging.debug("removed {} at %s".format(self.msg), *self.msg_args)
@contextmanager
def locked(self):
with self.in_folder.lock_for_key(self.key):
yield
def write(self, content):
folder = self.file.parent
folder.mkdir(parents=True, exist_ok=True)
self.file.write_text(ensure_text(json.dumps(content, sort_keys=True, indent=2)))
logging.debug("wrote {} at %s".format(self.msg), *self.msg_args)
class PyInfoStoreDisk(JSONStoreDisk):
def __init__(self, in_folder, path):
key = sha256(str(path).encode("utf-8") if six.PY3 else str(path)).hexdigest()
super(PyInfoStoreDisk, self).__init__(in_folder, key, "python info of %s", (path,))
class EmbedDistributionUpdateStoreDisk(JSONStoreDisk):
def __init__(self, in_folder, distribution):
super(EmbedDistributionUpdateStoreDisk, self).__init__(
in_folder,
distribution,
"embed update of distribution %s",
(distribution,),
)

View file

@ -0,0 +1,27 @@
from __future__ import absolute_import, unicode_literals
import logging
from tempfile import mkdtemp
from virtualenv.util.path import safe_delete
from .via_disk_folder import AppDataDiskFolder
class TempAppData(AppDataDiskFolder):
transient = True
can_update = False
def __init__(self):
super(TempAppData, self).__init__(folder=mkdtemp())
logging.debug("created temporary app data folder %s", self.lock.path)
def reset(self):
"""this is a temporary folder, is already empty to start with"""
def close(self):
logging.debug("remove temporary app data folder %s", self.lock.path)
safe_delete(self.lock.path)
def embed_update_log(self, distribution, for_py_version):
raise NotImplementedError

View file

@ -0,0 +1 @@
from __future__ import absolute_import, unicode_literals

View file

@ -0,0 +1 @@
from __future__ import absolute_import, unicode_literals

View file

@ -0,0 +1,124 @@
from __future__ import absolute_import, unicode_literals
import os
from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
from collections import OrderedDict
from virtualenv.config.convert import get_type
from ..env_var import get_env_var
from ..ini import IniConfig
class VirtualEnvOptions(Namespace):
def __init__(self, **kwargs):
super(VirtualEnvOptions, self).__init__(**kwargs)
self._src = None
self._sources = {}
def set_src(self, key, value, src):
setattr(self, key, value)
if src.startswith("env var"):
src = "env var"
self._sources[key] = src
def __setattr__(self, key, value):
if getattr(self, "_src", None) is not None:
self._sources[key] = self._src
super(VirtualEnvOptions, self).__setattr__(key, value)
def get_source(self, key):
return self._sources.get(key)
@property
def verbosity(self):
if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
return None
return max(self.verbose - self.quiet, 0)
def __repr__(self):
return "{}({})".format(
type(self).__name__,
", ".join("{}={}".format(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 variables
"""
def __init__(self, options=None, env=None, *args, **kwargs):
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(VirtualEnvConfigParser, self).__init__(*args, **kwargs)
self._fixed = set()
if options is not None and not isinstance(options, VirtualEnvOptions):
raise TypeError("options must be of type VirtualEnvOptions")
self.options = VirtualEnvOptions() if options is None else options
self._interpreter = None
self._app_data = None
def _fix_defaults(self):
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):
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, action.default_source = outcome
else:
outcome = action.default, "default"
self.options.set_src(action.dest, *outcome)
def enable_help(self):
self._fix_defaults()
self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")
def parse_known_args(self, args=None, namespace=None):
if namespace is None:
namespace = self.options
elif namespace is not self.options:
raise ValueError("can only pass in parser.options")
self._fix_defaults()
self.options._src = "cli"
try:
namespace.env = self.env
return super(VirtualEnvConfigParser, self).parse_known_args(args, namespace=namespace)
finally:
self.options._src = None
class HelpFormatter(ArgumentDefaultsHelpFormatter):
def __init__(self, prog):
super(HelpFormatter, self).__init__(prog, max_help_position=32, width=240)
def _get_help_string(self, action):
# noinspection PyProtectedMember
text = super(HelpFormatter, self)._get_help_string(action)
if hasattr(action, "default_source"):
default = " (default: %(default)s)"
if text.endswith(default):
text = "{} (default: %(default)s -> from %(default_source)s)".format(text[: -len(default)])
return text

View file

@ -0,0 +1,98 @@
from __future__ import absolute_import, unicode_literals
import logging
import os
class TypeData(object):
def __init__(self, default_type, as_type):
self.default_type = default_type
self.as_type = as_type
def __repr__(self):
return "{}(base={}, as={})".format(self.__class__.__name__, self.default_type, self.as_type)
def convert(self, value):
return self.default_type(value)
class BoolType(TypeData):
BOOLEAN_STATES = {
"1": True,
"yes": True,
"true": True,
"on": True,
"0": False,
"no": False,
"false": False,
"off": False,
}
def convert(self, value):
if value.lower() not in self.BOOLEAN_STATES:
raise ValueError("Not a boolean: %s" % value)
return self.BOOLEAN_STATES[value.lower()]
class NoneType(TypeData):
def convert(self, value):
if not value:
return None
return str(value)
class ListType(TypeData):
def _validate(self):
""" """
def convert(self, value, flatten=True):
values = self.split_values(value)
result = []
for value in values:
sub_values = value.split(os.pathsep)
result.extend(sub_values)
converted = [self.as_type(i) for i in result]
return converted
def split_values(self, value):
"""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(",")
values = filter(None, [x.strip() for x in values])
else:
values = list(value)
return values
def convert(value, as_type, source):
"""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:
logging.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):
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)
__all__ = (
"convert",
"get_type",
)

View file

@ -0,0 +1,28 @@
from __future__ import absolute_import, unicode_literals
from virtualenv.util.six import ensure_str, ensure_text
from .convert import convert
def get_env_var(key, as_type, env):
"""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
:return:
"""
environ_key = ensure_str("VIRTUALENV_{}".format(key.upper()))
if env.get(environ_key):
value = env[environ_key]
# noinspection PyBroadException
try:
source = "env var {}".format(ensure_text(environ_key))
as_type = convert(value, as_type, source)
return as_type, source
except Exception: # note the converter already logs a warning when failures happen
pass
__all__ = ("get_env_var",)

View file

@ -0,0 +1,84 @@
from __future__ import absolute_import, unicode_literals
import logging
import os
from platformdirs import user_config_dir
from virtualenv.info import PY3
from virtualenv.util import ConfigParser
from virtualenv.util.path import Path
from virtualenv.util.six import ensure_str
from .convert import convert
class IniConfig(object):
VIRTUALENV_CONFIG_FILE_ENV_VAR = ensure_str("VIRTUALENV_CONFIG_FILE")
STATE = {None: "failed to parse", True: "active", False: "missing"}
section = "virtualenv"
def __init__(self, env=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
config_file = (
Path(config_file)
if config_file is not None
else Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini"
)
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.ConfigParser()
try:
self._load()
self.has_virtualenv_section = self.config_parser.has_section(self.section)
except Exception as exc:
exception = exc
if exception is not None:
logging.error("failed to read config file %s because %r", config_file, exception)
def _load(self):
with self.config_file.open("rt") as file_handler:
reader = getattr(self.config_parser, "read_file" if PY3 else "readfp")
reader(file_handler)
def get(self, key, as_type):
cache_key = key, as_type
if cache_key in self._cache:
return self._cache[cache_key]
# noinspection PyBroadException
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:
result = None
self._cache[cache_key] = result
return result
def __bool__(self):
return bool(self.has_config_file) and bool(self.has_virtualenv_section)
@property
def epilog(self):
msg = "{}config file {} {} (change{} via env var {})"
return msg.format(
"\n",
self.config_file,
self.STATE[self.has_config_file],
"d" if self.is_env_var else "",
self.VIRTUALENV_CONFIG_FILE_ENV_VAR,
)

View file

@ -0,0 +1 @@
from __future__ import absolute_import, unicode_literals

View file

@ -0,0 +1,239 @@
from __future__ import absolute_import, print_function, unicode_literals
import json
import logging
import os
import sys
from abc import ABCMeta, abstractmethod
from argparse import ArgumentTypeError
from ast import literal_eval
from collections import OrderedDict
from textwrap import dedent
from six import add_metaclass
from virtualenv.discovery.cached_py_info import LogCmd
from virtualenv.info import WIN_CPYTHON_2
from virtualenv.util.path import Path, safe_delete
from virtualenv.util.six import ensure_str, ensure_text
from virtualenv.util.subprocess import run_cmd
from virtualenv.version import __version__
from .pyenv_cfg import PyEnvCfg
HERE = Path(os.path.abspath(__file__)).parent
DEBUG_SCRIPT = HERE / "debug.py"
class CreatorMeta(object):
def __init__(self):
self.error = None
@add_metaclass(ABCMeta)
class Creator(object):
"""A class that given a python Interpreter creates a virtual environment"""
def __init__(self, options, interpreter):
"""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
def __repr__(self):
return ensure_str(self.__unicode__())
def __unicode__(self):
return "{}({})".format(self.__class__.__name__, ", ".join("{}={}".format(k, v) for k, v in self._args()))
def _args(self):
return [
("dest", ensure_text(str(self.dest))),
("clear", self.clear),
("no_vcs_ignore", self.no_vcs_ignore),
]
@classmethod
def can_create(cls, interpreter):
"""Determine if we can create a virtual environment.
:param interpreter: the interpreter in question
:return: ``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, interpreter, meta, app_data):
"""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):
"""Perform the virtual environment creation."""
raise NotImplementedError
@classmethod
def validate_dest(cls, raw_value):
"""No path separator in the path, valid chars and must be write-able"""
def non_write_able(dest, value):
common = Path(*os.path.commonprefix([value.parts, dest.parts]))
raise ArgumentTypeError(
"the destination {} is not write-able at {}".format(dest.relative_to(common), common),
)
# 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 ensure_text(raw_value):
try:
trip = char.encode(encoding, **kwargs).decode(encoding)
if trip == char:
continue
raise ValueError(trip)
except ValueError:
refused[char] = None
if refused:
raise ArgumentTypeError(
"the file system codec ({}) cannot handle characters {!r} within {!r}".format(
encoding,
"".join(refused.keys()),
raw_value,
),
)
if os.pathsep in raw_value:
raise ArgumentTypeError(
"destination {!r} must not contain the path separator ({}) as this would break "
"the activation scripts".format(raw_value, os.pathsep),
)
value = Path(raw_value)
if value.exists() and value.is_file():
raise ArgumentTypeError("the destination {} already exists and is a file".format(value))
if (3, 3) <= sys.version_info <= (3, 6):
# pre 3.6 resolve is always strict, aka must exists, sidestep by using os.path operation
dest = Path(os.path.realpath(raw_value))
else:
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(ensure_text(str(dest)), os.W_OK):
break
else:
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):
if self.dest.exists() and self.clear:
logging.debug("delete %s", self.dest)
safe_delete(self.dest)
self.create()
self.set_pyenv_cfg()
if not self.no_vcs_ignore:
self.setup_ignore_vcs()
def set_pyenv_cfg(self):
self.pyenv_cfg.content = OrderedDict()
self.pyenv_cfg["home"] = self.interpreter.system_exec_prefix
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["virtualenv"] = __version__
def setup_ignore_vcs(self):
"""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(
dedent(
"""
# created by virtualenv automatically
*
""",
).lstrip(),
)
# 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):
"""
:return: 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
# noinspection PyMethodMayBeStatic
def debug_script(self):
return DEBUG_SCRIPT
def get_env_debug_info(env_exe, debug_script, app_data, env):
env = env.copy()
env.pop(str("PYTHONPATH"), None)
with app_data.ensure_extracted(debug_script) as debug_script:
cmd = [str(env_exe), str(debug_script)]
if WIN_CPYTHON_2:
cmd = [ensure_text(i) for i in cmd]
logging.debug(str("debug via %r"), LogCmd(cmd))
code, out, err = run_cmd(cmd)
# noinspection PyBroadException
try:
if code != 0:
result = literal_eval(out)
else:
result = json.loads(out)
if err:
result["err"] = err
except Exception as exception:
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

View file

@ -0,0 +1,110 @@
"""Inspect a target Python interpreter virtual environment wise"""
import sys # built-in
PYPY2_WIN = hasattr(sys, "pypy_version_info") and sys.platform != "win32" and sys.version_info[0] == 2
def encode_path(value):
if value is None:
return None
if not isinstance(value, (str, bytes)):
if isinstance(value, type):
value = repr(value)
else:
value = repr(type(value))
if isinstance(value, bytes) and not PYPY2_WIN:
value = value.decode(sys.getfilesystemencoding())
return value
def encode_list_path(value):
return [encode_path(i) for i in value]
def run():
"""print debug data about the virtual environment"""
try:
from collections import OrderedDict
except ImportError: # pragma: no cover
# this is possible if the standard library cannot be accessed
# noinspection PyPep8Naming
OrderedDict = dict # pragma: no cover
result = OrderedDict([("sys", OrderedDict())])
path_keys = (
"executable",
"_base_executable",
"prefix",
"base_prefix",
"real_prefix",
"exec_prefix",
"base_exec_prefix",
"path",
"meta_path",
)
for key in path_keys:
value = getattr(sys, key, None)
if isinstance(value, list):
value = encode_list_path(value)
else:
value = encode_path(value)
result["sys"][key] = value
result["sys"]["fs_encoding"] = sys.getfilesystemencoding()
result["sys"]["io_encoding"] = getattr(sys.stdout, "encoding", None)
result["version"] = sys.version
try:
import sysconfig
# https://bugs.python.org/issue22199
makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None))
result["makefile_filename"] = encode_path(makefile())
except ImportError:
pass
import os # landmark
result["os"] = repr(os)
try:
# noinspection PyUnresolvedReferences
import site # site
result["site"] = repr(site)
except ImportError as exception: # pragma: no cover
result["site"] = repr(exception) # pragma: no cover
try:
# noinspection PyUnresolvedReferences
import datetime # site
result["datetime"] = repr(datetime)
except ImportError as exception: # pragma: no cover
result["datetime"] = repr(exception) # pragma: no cover
try:
# noinspection PyUnresolvedReferences
import math # site
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
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) # pragma: no cover
if __name__ == "__main__":
run()

View file

@ -0,0 +1,117 @@
from __future__ import absolute_import, print_function, unicode_literals
from abc import ABCMeta
from collections import OrderedDict
from six import add_metaclass
from virtualenv.info import IS_WIN
from virtualenv.util.path import Path
from virtualenv.util.six import ensure_text
@add_metaclass(ABCMeta)
class Describe(object):
"""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, interpreter):
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):
return self.script_dir
@property
def script_dir(self):
return self.dest / self.interpreter.install_path("scripts")
@property
def purelib(self):
return self.dest / self.interpreter.install_path("purelib")
@property
def platlib(self):
return self.dest / self.interpreter.install_path("platlib")
@property
def libs(self):
return list(OrderedDict(((self.platlib, None), (self.purelib, None))).keys())
@property
def stdlib(self):
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):
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):
if self._conf_vars is None:
self._conf_vars = self._calc_config_vars(ensure_text(str(self.dest)))
return self._conf_vars
def _calc_config_vars(self, to):
return {
k: (to if v.startswith(self.interpreter.prefix) else v) for k, v in self.interpreter.sysconfig_vars.items()
}
@classmethod
def can_describe(cls, interpreter):
"""Knows means it knows how the output will look"""
return True
@property
def env_name(self):
return ensure_text(self.dest.parts[-1])
@property
def exe(self):
return self.bin_dir / "{}{}".format(self.exe_stem(), self.suffix)
@classmethod
def exe_stem(cls):
"""executable name without suffix - there seems to be no standard way to get this without creating it"""
raise NotImplementedError
def script(self, name):
return self.script_dir / "{}{}".format(name, self.suffix)
@add_metaclass(ABCMeta)
class Python2Supports(Describe):
@classmethod
def can_describe(cls, interpreter):
return interpreter.version_info.major == 2 and super(Python2Supports, cls).can_describe(interpreter)
@add_metaclass(ABCMeta)
class Python3Supports(Describe):
@classmethod
def can_describe(cls, interpreter):
return interpreter.version_info.major == 3 and super(Python3Supports, cls).can_describe(interpreter)
@add_metaclass(ABCMeta)
class PosixSupports(Describe):
@classmethod
def can_describe(cls, interpreter):
return interpreter.os == "posix" and super(PosixSupports, cls).can_describe(interpreter)
@add_metaclass(ABCMeta)
class WindowsSupports(Describe):
@classmethod
def can_describe(cls, interpreter):
return interpreter.os == "nt" and super(WindowsSupports, cls).can_describe(interpreter)

View file

@ -0,0 +1,61 @@
from __future__ import absolute_import, unicode_literals
import logging
from collections import OrderedDict
from virtualenv.util.six import ensure_text
class PyEnvCfg(object):
def __init__(self, content, path):
self.content = content
self.path = path
@classmethod
def from_folder(cls, folder):
return cls.from_file(folder / "pyvenv.cfg")
@classmethod
def from_file(cls, path):
content = cls._read_values(path) if path.exists() else OrderedDict()
return PyEnvCfg(content, path)
@staticmethod
def _read_values(path):
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()
content[key] = value
return content
def write(self):
logging.debug("write %s", ensure_text(str(self.path)))
text = ""
for key, value in self.content.items():
line = "{} = {}".format(key, value)
logging.debug("\t%s", line)
text += line
text += "\n"
self.path.write_text(text, encoding="utf-8")
def refresh(self):
self.content = self._read_values(self.path)
return self.content
def __setitem__(self, key, value):
self.content[key] = value
def __getitem__(self, key):
return self.content[key]
def __contains__(self, item):
return item in self.content
def update(self, other):
self.content.update(other)
return self
def __repr__(self):
return "{}(path={})".format(self.__class__.__name__, self.path)

View file

@ -0,0 +1,130 @@
"""Patches that are applied at runtime to the virtual environment"""
# -*- coding: utf-8 -*-
import os
import sys
VIRTUALENV_PATCH_FILE = os.path.join(__file__)
def patch_dist(dist):
"""
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, **kwargs):
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 = "install_{}".format(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"
if sys.version_info > (3, 4):
# 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 = []
def find_spec(self, fullname, path, target=None):
if fullname in _DISTUTILS_PATCH and self.fullname is None:
# initialize lock[0] lazily
if len(self.lock) == 0:
import threading
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
from importlib.util import find_spec
with self.lock[0]:
self.fullname = fullname
try:
spec = find_spec(fullname, path)
if spec is not None:
# https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
is_new_api = hasattr(spec.loader, "exec_module")
func_name = "exec_module" if is_new_api else "load_module"
old = getattr(spec.loader, func_name)
func = self.exec_module if is_new_api else self.load_module
if old is not func:
try:
setattr(spec.loader, func_name, partial(func, old))
except AttributeError:
pass # C-Extension loaders are r/o such as zipimporter with <python 3.7
return spec
finally:
self.fullname = None
@staticmethod
def exec_module(old, module):
old(module)
if module.__name__ in _DISTUTILS_PATCH:
patch_dist(module)
@staticmethod
def load_module(old, name):
module = old(name)
if module.__name__ in _DISTUTILS_PATCH:
patch_dist(module)
return module
sys.meta_path.insert(0, _Finder())
else:
# https://www.python.org/dev/peps/pep-0302/
from imp import find_module
from pkgutil import ImpImporter, ImpLoader
class _VirtualenvImporter(object, ImpImporter):
def __init__(self, path=None):
object.__init__(self)
ImpImporter.__init__(self, path)
def find_module(self, fullname, path=None):
if fullname in _DISTUTILS_PATCH:
try:
return _VirtualenvLoader(fullname, *find_module(fullname.split(".")[-1], path))
except ImportError:
pass
return None
class _VirtualenvLoader(object, ImpLoader):
def __init__(self, fullname, file, filename, etc):
object.__init__(self)
ImpLoader.__init__(self, fullname, file, filename, etc)
def load_module(self, fullname):
module = super(_VirtualenvLoader, self).load_module(fullname)
patch_dist(module)
module.__loader__ = None # distlib fallback
return module
sys.meta_path.append(_VirtualenvImporter())

View file

@ -0,0 +1,112 @@
from __future__ import absolute_import, unicode_literals
import logging
import os
from abc import ABCMeta
from six import add_metaclass
from virtualenv.info import fs_supports_symlink
from virtualenv.util.path import Path
from virtualenv.util.six import ensure_text
from ..creator import Creator, CreatorMeta
class ViaGlobalRefMeta(CreatorMeta):
def __init__(self):
super(ViaGlobalRefMeta, self).__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):
return not self.copy_error
@property
def can_symlink(self):
return not self.symlink_error
@add_metaclass(ABCMeta)
class ViaGlobalRefApi(Creator):
def __init__(self, options, interpreter):
super(ViaGlobalRefApi, self).__init__(options, interpreter)
self.symlinks = self._should_symlink(options)
self.enable_system_site_package = options.system_site
@staticmethod
def _should_symlink(options):
# 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, interpreter, meta, app_data):
super(ViaGlobalRefApi, cls).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",
)
group = parser.add_mutually_exclusive_group()
if not meta.can_symlink and not meta.can_copy:
raise RuntimeError("neither symlink or copy method supported")
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):
self.install_patch()
def install_patch(self):
text = self.env_patch_text()
if text:
pth = self.purelib / "_virtualenv.pth"
logging.debug("create virtualenv import hook file %s", ensure_text(str(pth)))
pth.write_text("import _virtualenv")
dest_path = self.purelib / "_virtualenv.py"
logging.debug("create %s", ensure_text(str(dest_path)))
dest_path.write_text(text)
def env_patch_text(self):
"""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:
text = resolved_path.read_text()
return text.replace('"__SCRIPT_DIR__"', repr(os.path.relpath(str(self.script_dir), str(self.purelib))))
def _args(self):
return super(ViaGlobalRefApi, self)._args() + [("global", self.enable_system_site_package)]
def set_pyenv_cfg(self):
super(ViaGlobalRefApi, self).set_pyenv_cfg()
self.pyenv_cfg["include-system-site-packages"] = "true" if self.enable_system_site_package else "false"

View file

@ -0,0 +1,17 @@
from __future__ import absolute_import, unicode_literals
from abc import ABCMeta
from six import add_metaclass
from virtualenv.create.creator import Creator
from virtualenv.create.describe import Describe
@add_metaclass(ABCMeta)
class VirtualenvBuiltin(Creator, Describe):
"""A creator that does operations itself without delegation, if we can create it we can also describe it"""
def __init__(self, options, interpreter):
Creator.__init__(self, options, interpreter)
Describe.__init__(self, self.dest, interpreter)

View file

@ -0,0 +1 @@
from __future__ import absolute_import, unicode_literals

View file

@ -0,0 +1,65 @@
from __future__ import absolute_import, unicode_literals
from abc import ABCMeta
from collections import OrderedDict
from six import add_metaclass
from virtualenv.create.describe import PosixSupports, WindowsSupports
from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen
from virtualenv.util.path import Path
from ..via_global_self_do import ViaGlobalRefVirtualenvBuiltin
@add_metaclass(ABCMeta)
class CPython(ViaGlobalRefVirtualenvBuiltin):
@classmethod
def can_describe(cls, interpreter):
return interpreter.implementation == "CPython" and super(CPython, cls).can_describe(interpreter)
@classmethod
def exe_stem(cls):
return "python"
@add_metaclass(ABCMeta)
class CPythonPosix(CPython, PosixSupports):
"""Create a CPython virtual environment on POSIX platforms"""
@classmethod
def _executables(cls, interpreter):
host_exe = Path(interpreter.system_executable)
major, minor = interpreter.version_info.major, interpreter.version_info.minor
targets = OrderedDict(
(i, None) for i in ["python", "python{}".format(major), "python{}.{}".format(major, minor), host_exe.name]
)
must = RefMust.COPY if interpreter.version_info.major == 2 else RefMust.NA
yield host_exe, list(targets.keys()), must, RefWhen.ANY
@add_metaclass(ABCMeta)
class CPythonWindows(CPython, WindowsSupports):
@classmethod
def _executables(cls, interpreter):
# symlink of the python executables does not work reliably, copy always instead
# - https://bugs.python.org/issue42013
# - venv
host = cls.host_python(interpreter)
for path in (host.parent / n for n in {"python.exe", host.name}):
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], RefMust.COPY, RefWhen.ANY
@classmethod
def host_python(cls, interpreter):
return Path(interpreter.system_executable)
def is_mac_os_framework(interpreter):
if interpreter.platform == "darwin":
framework_var = interpreter.sysconfig_vars.get("PYTHONFRAMEWORK")
value = "Python3" if interpreter.version_info.major == 3 else "Python"
return framework_var == value
return False

View file

@ -0,0 +1,102 @@
from __future__ import absolute_import, unicode_literals
import abc
import logging
from six import add_metaclass
from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
from virtualenv.util.path import Path
from ..python2.python2 import Python2
from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework
@add_metaclass(abc.ABCMeta)
class CPython2(CPython, Python2):
"""Create a CPython version 2 virtual environment"""
@classmethod
def sources(cls, interpreter):
for src in super(CPython2, cls).sources(interpreter):
yield src
# include folder needed on Python 2 as we don't have pyenv.cfg
host_include_marker = cls.host_include_marker(interpreter)
if host_include_marker.exists():
yield PathRefToDest(host_include_marker.parent, dest=lambda self, _: self.include)
@classmethod
def needs_stdlib_py_module(cls):
return False
@classmethod
def host_include_marker(cls, interpreter):
return Path(interpreter.system_include) / "Python.h"
@property
def include(self):
# the pattern include the distribution name too at the end, remove that via the parent call
return (self.dest / self.interpreter.install_path("headers")).parent
@classmethod
def modules(cls):
return [
"os", # landmark to set sys.prefix
]
def ensure_directories(self):
dirs = super(CPython2, self).ensure_directories()
host_include_marker = self.host_include_marker(self.interpreter)
if host_include_marker.exists():
dirs.add(self.include.parent)
else:
logging.debug("no include folders as can't find include marker %s", host_include_marker)
return dirs
@add_metaclass(abc.ABCMeta)
class CPython2PosixBase(CPython2, CPythonPosix):
"""common to macOs framework builds and other posix CPython2"""
@classmethod
def sources(cls, interpreter):
for src in super(CPython2PosixBase, cls).sources(interpreter):
yield src
# check if the makefile exists and if so make it available under the virtual environment
make_file = Path(interpreter.sysconfig["makefile_filename"])
if make_file.exists() and str(make_file).startswith(interpreter.prefix):
under_prefix = make_file.relative_to(Path(interpreter.prefix))
yield PathRefToDest(make_file, dest=lambda self, s: self.dest / under_prefix)
class CPython2Posix(CPython2PosixBase):
"""CPython 2 on POSIX (excluding macOs framework builds)"""
@classmethod
def can_describe(cls, interpreter):
return is_mac_os_framework(interpreter) is False and super(CPython2Posix, cls).can_describe(interpreter)
@classmethod
def sources(cls, interpreter):
for src in super(CPython2Posix, cls).sources(interpreter):
yield src
# landmark for exec_prefix
exec_marker_file, to_path, _ = cls.from_stdlib(cls.mappings(interpreter), "lib-dynload")
yield PathRefToDest(exec_marker_file, dest=to_path)
class CPython2Windows(CPython2, CPythonWindows):
"""CPython 2 on Windows"""
@classmethod
def sources(cls, interpreter):
for src in super(CPython2Windows, cls).sources(interpreter):
yield src
py27_dll = Path(interpreter.system_executable).parent / "python27.dll"
if py27_dll.exists(): # this might be global in the Windows folder in which case it's alright to be missing
yield PathRefToDest(py27_dll, dest=cls.to_bin)
libs = Path(interpreter.system_prefix) / "libs"
if libs.exists():
yield PathRefToDest(libs, dest=lambda self, s: self.dest / s.name)

View file

@ -0,0 +1,91 @@
from __future__ import absolute_import, unicode_literals
import abc
from textwrap import dedent
from six import add_metaclass
from virtualenv.create.describe import Python3Supports
from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
from virtualenv.create.via_global_ref.store import is_store_python
from virtualenv.util.path import Path
from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework
@add_metaclass(abc.ABCMeta)
class CPython3(CPython, Python3Supports):
""" """
class CPython3Posix(CPythonPosix, CPython3):
@classmethod
def can_describe(cls, interpreter):
return is_mac_os_framework(interpreter) is False and super(CPython3Posix, cls).can_describe(interpreter)
def env_patch_text(self):
text = super(CPython3Posix, self).env_patch_text()
if self.pyvenv_launch_patch_active(self.interpreter):
text += dedent(
"""
# for https://github.com/python/cpython/pull/9516, see https://github.com/pypa/virtualenv/issues/1704
import os
if "__PYVENV_LAUNCHER__" in os.environ:
del os.environ["__PYVENV_LAUNCHER__"]
""",
)
return text
@classmethod
def pyvenv_launch_patch_active(cls, interpreter):
ver = interpreter.version_info
return interpreter.platform == "darwin" and ((3, 7, 8) > ver >= (3, 7) or (3, 8, 3) > ver >= (3, 8))
class CPython3Windows(CPythonWindows, CPython3):
""" """
@classmethod
def setup_meta(cls, interpreter):
if is_store_python(interpreter): # store python is not supported here
return None
return super(CPython3Windows, cls).setup_meta(interpreter)
@classmethod
def sources(cls, interpreter):
for src in super(CPython3Windows, cls).sources(interpreter):
yield src
if not cls.has_shim(interpreter):
for src in cls.include_dll_and_pyd(interpreter):
yield src
@classmethod
def has_shim(cls, interpreter):
return interpreter.version_info.minor >= 7 and cls.shim(interpreter) is not None
@classmethod
def shim(cls, interpreter):
shim = Path(interpreter.system_stdlib) / "venv" / "scripts" / "nt" / "python.exe"
if shim.exists():
return shim
return None
@classmethod
def host_python(cls, interpreter):
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)
return super(CPython3Windows, cls).host_python(interpreter)
@classmethod
def include_dll_and_pyd(cls, interpreter):
dll_folder = Path(interpreter.system_prefix) / "DLLs"
host_exe_folder = Path(interpreter.system_executable).parent
for folder in [host_exe_folder, dll_folder]:
for file in folder.iterdir():
if file.suffix in (".pyd", ".dll"):
yield PathRefToDest(file, dest=cls.to_dll_and_pyd)
def to_dll_and_pyd(self, src):
return self.bin_dir / src.name

View file

@ -0,0 +1,341 @@
# -*- coding: utf-8 -*-
"""The Apple Framework builds require their own customization"""
import logging
import os
import struct
import subprocess
from abc import ABCMeta, abstractmethod
from textwrap import dedent
from six import add_metaclass, text_type
from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest, RefMust
from virtualenv.info import IS_MAC_ARM64
from virtualenv.util.path import Path
from virtualenv.util.six import ensure_text
from .common import CPython, CPythonPosix, is_mac_os_framework
from .cpython2 import CPython2PosixBase
from .cpython3 import CPython3
@add_metaclass(ABCMeta)
class CPythonmacOsFramework(CPython):
@classmethod
def can_describe(cls, interpreter):
return is_mac_os_framework(interpreter) and super(CPythonmacOsFramework, cls).can_describe(interpreter)
@classmethod
def sources(cls, interpreter):
for src in super(CPythonmacOsFramework, cls).sources(interpreter):
yield src
# add a symlink to the host python image
exe = cls.image_ref(interpreter)
ref = PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK)
yield ref
def create(self):
super(CPythonmacOsFramework, self).create()
# change the install_name of the copied python executables
target = "@executable_path/../.Python"
current = self.current_mach_o_image_path()
for src in self._sources:
if isinstance(src, ExePathRefToDest):
if 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)
@classmethod
def _executables(cls, interpreter):
for _, targets, must, when in super(CPythonmacOsFramework, cls)._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"
yield fixed_host_exe, targets, must, when
@abstractmethod
def current_mach_o_image_path(self):
raise NotImplementedError
@classmethod
def image_ref(cls, interpreter):
raise NotImplementedError
class CPython2macOsFramework(CPythonmacOsFramework, CPython2PosixBase):
@classmethod
def can_create(cls, interpreter):
if not IS_MAC_ARM64 and super(CPython2macOsFramework, cls).can_describe(interpreter):
return super(CPython2macOsFramework, cls).can_create(interpreter)
return False
@classmethod
def image_ref(cls, interpreter):
return Path(interpreter.prefix) / "Python"
def current_mach_o_image_path(self):
return os.path.join(self.interpreter.prefix, "Python")
@classmethod
def sources(cls, interpreter):
for src in super(CPython2macOsFramework, cls).sources(interpreter):
yield src
# landmark for exec_prefix
exec_marker_file, to_path, _ = cls.from_stdlib(cls.mappings(interpreter), "lib-dynload")
yield PathRefToDest(exec_marker_file, dest=to_path)
@property
def reload_code(self):
result = super(CPython2macOsFramework, self).reload_code
result = dedent(
"""
# the bundled site.py always adds the global site package if we're on python framework build, escape this
import sysconfig
config = sysconfig.get_config_vars()
before = config["PYTHONFRAMEWORK"]
try:
config["PYTHONFRAMEWORK"] = ""
{}
finally:
config["PYTHONFRAMEWORK"] = before
""".format(
result,
),
)
return result
class CPython2macOsArmFramework(CPython2macOsFramework, CPythonmacOsFramework, CPython2PosixBase):
@classmethod
def can_create(cls, interpreter):
if IS_MAC_ARM64 and super(CPythonmacOsFramework, cls).can_describe(interpreter):
return super(CPythonmacOsFramework, cls).can_create(interpreter)
return False
def create(self):
super(CPython2macOsFramework, self).create()
self.fix_signature()
def fix_signature(self):
"""
On Apple M1 machines (arm64 chips), rewriting the python executable invalidates its signature.
In python2 this results in a unusable python exe which just dies.
As a temporary workaround we can codesign the python exe during the creation process.
"""
exe = self.exe
try:
logging.debug("Changing signature of copied python exe %s", exe)
bak_dir = exe.parent / "bk"
# Reset the signing on Darwin since the exe has been modified.
# Note codesign fails on the original exe, it needs to be copied and moved back.
bak_dir.mkdir(parents=True, exist_ok=True)
subprocess.check_call(["cp", text_type(exe), text_type(bak_dir)])
subprocess.check_call(["mv", text_type(bak_dir / exe.name), text_type(exe)])
bak_dir.rmdir()
metadata = "--preserve-metadata=identifier,entitlements,flags,runtime"
cmd = ["codesign", "-s", "-", metadata, "-f", text_type(exe)]
logging.debug("Changing Signature: %s", cmd)
subprocess.check_call(cmd)
except Exception:
logging.fatal("Could not change MacOS code signing on copied python exe at %s", exe)
raise
class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix):
@classmethod
def image_ref(cls, interpreter):
return Path(interpreter.prefix) / "Python3"
def current_mach_o_image_path(self):
return "@executable_path/../../../../Python3"
@property
def reload_code(self):
result = super(CPython3macOsFramework, self).reload_code
result = dedent(
"""
# 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
{}
finally:
sys._framework = before
""".format(
result,
),
)
return result
def fix_mach_o(exe, current, new, max_size):
"""
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 structurea list of variable-length "load commands" that reference pages of data elsewhere in the filewas
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:
logging.debug("change Mach-O for %s from %s to %s", ensure_text(exe), current, ensure_text(new))
_builtin_change_mach_o(max_size)(exe, current, new)
except Exception as e:
logging.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):
MH_MAGIC = 0xFEEDFACE
MH_CIGAM = 0xCEFAEDFE
MH_MAGIC_64 = 0xFEEDFACF
MH_CIGAM_64 = 0xCFFAEDFE
FAT_MAGIC = 0xCAFEBABE
BIG_ENDIAN = ">"
LITTLE_ENDIAN = "<"
LC_LOAD_DYLIB = 0xC
class FileView(object):
"""A proxy for file-like objects that exposes a given view of a file. Modified from macholib."""
def __init__(self, file_obj, start=0, size=maxint):
if isinstance(file_obj, FileView):
self._file_obj = file_obj._file_obj
else:
self._file_obj = file_obj
self._start = start
self._end = start + size
self._pos = 0
def __repr__(self):
return "<fileview [{:d}, {:d}] {!r}>".format(self._start, self._end, self._file_obj)
def tell(self):
return self._pos
def _checkwindow(self, seek_to, op):
if not (self._start <= seek_to <= self._end):
msg = "{} to offset {:d} is outside window [{:d}, {:d}]".format(op, seek_to, self._start, self._end)
raise IOError(msg)
def seek(self, offset, whence=0):
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:
raise IOError("Invalid whence argument to seek: {!r}".format(whence))
self._checkwindow(seek_to, "seek")
self._file_obj.seek(seek_to)
self._pos = seek_to - self._start
def write(self, content):
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=maxint):
assert size >= 0
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, endian, num=1):
"""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, what, value):
"""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, bits, endian):
# 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)
# 64-bits header has one more field.
if bits == 64:
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)
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)
# Read the NUL terminated string
load = file.read(cmd_size - name_offset).decode()
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)
file.write(value.encode() + b"\0")
# Seek to the next command
file.seek(where + cmd_size, os.SEEK_SET)
def do_file(file, offset=0, size=maxint):
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):
# Read arch header
cpu_type, cpu_sub_type, offset, size, align = read_data(file, BIG_ENDIAN, 5)
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)
with open(at_path, "r+b") as f:
do_file(f)
return mach_o_change

Some files were not shown because too many files have changed in this diff Show more