summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVinay Sajip <vinay_sajip@yahoo.co.uk>2012-05-26 03:45:29 +0100
committerVinay Sajip <vinay_sajip@yahoo.co.uk>2012-05-26 03:45:29 +0100
commitbd885cf8bc59f7621b62029065a7bbfe7ab3b9fc (patch)
treeb4509ec6075d931aee2e92e6affe2a16eb578287
parenta124eb0185543ea02b835a6a41f5128c0a2943f5 (diff)
downloadcpython-bd885cf8bc59f7621b62029065a7bbfe7ab3b9fc.tar.gz
Implemented PEP 405 (Python virtual environments).
-rw-r--r--Doc/library/development.rst1
-rw-r--r--Doc/library/sys.rst28
-rw-r--r--Doc/library/venv.rst193
-rw-r--r--Lib/distutils/sysconfig.py40
-rw-r--r--Lib/gettext.py2
-rw-r--r--Lib/idlelib/EditorWindow.py6
-rw-r--r--Lib/packaging/command/build_ext.py5
-rwxr-xr-xLib/pydoc.py2
-rw-r--r--Lib/site.py88
-rw-r--r--Lib/subprocess.py2
-rw-r--r--Lib/sysconfig.cfg24
-rw-r--r--Lib/sysconfig.py29
-rwxr-xr-xLib/test/regrtest.py2
-rw-r--r--Lib/test/test_cmd.py2
-rw-r--r--Lib/test/test_doctest.py2
-rw-r--r--Lib/test/test_subprocess.py4
-rw-r--r--Lib/test/test_sys.py4
-rw-r--r--Lib/test/test_sysconfig.py11
-rw-r--r--Lib/test/test_trace.py4
-rw-r--r--Lib/test/test_venv.py139
-rw-r--r--Lib/tkinter/_fix.py4
-rw-r--r--Lib/trace.py8
-rw-r--r--Lib/venv/__init__.py502
-rw-r--r--Lib/venv/__main__.py10
-rw-r--r--Lib/venv/scripts/nt/Activate.ps134
-rw-r--r--Lib/venv/scripts/nt/Deactivate.ps119
-rw-r--r--Lib/venv/scripts/nt/activate.bat31
-rw-r--r--Lib/venv/scripts/nt/deactivate.bat17
-rw-r--r--Lib/venv/scripts/nt/pysetup3-script.py11
-rw-r--r--Lib/venv/scripts/nt/pysetup3.exebin0 -> 6144 bytes
-rw-r--r--Lib/venv/scripts/posix/activate76
-rw-r--r--Lib/venv/scripts/posix/pysetup311
-rw-r--r--Mac/Makefile.in4
-rw-r--r--Mac/Tools/pythonw.c12
-rw-r--r--Makefile.pre.in3
-rw-r--r--Modules/getpath.c86
-rw-r--r--PC/getpathp.c81
-rw-r--r--Python/sysmodule.c4
-rw-r--r--Tools/msi/msi.py1
-rwxr-xr-xTools/scripts/pyvenv11
-rw-r--r--setup.py7
41 files changed, 1454 insertions, 66 deletions
diff --git a/Doc/library/development.rst b/Doc/library/development.rst
index 06e7048a04..2368769c41 100644
--- a/Doc/library/development.rst
+++ b/Doc/library/development.rst
@@ -23,3 +23,4 @@ The list of modules described in this chapter is:
unittest.mock-examples.rst
2to3.rst
test.rst
+ venv.rst
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 96450c5043..1ba9005c61 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -29,6 +29,26 @@ always available.
command line, see the :mod:`fileinput` module.
+.. data:: base_exec_prefix
+
+ Set during Python startup, before ``site.py`` is run, to the same value as
+ :data:`exec_prefix`. If not running in a virtual environment, the values
+ will stay the same; if ``site.py`` finds that a virtual environment is in
+ use, the values of :data:`prefix` and :data:`exec_prefix` will be changed to
+ point to the virtual environment, whereas :data:`base_prefix` and
+ :data:`base_exec_prefix` will remain pointing to the base Python
+ installation (the one which the virtual environment was created from).
+
+.. data:: base_prefix
+
+ Set during Python startup, before ``site.py`` is run, to the same value as
+ :data:`prefix`. If not running in a virtual environment, the values
+ will stay the same; if ``site.py`` finds that a virtual environment is in
+ use, the values of :data:`prefix` and :data:`exec_prefix` will be changed to
+ point to the virtual environment, whereas :data:`base_prefix` and
+ :data:`base_exec_prefix` will remain pointing to the base Python
+ installation (the one which the virtual environment was created from).
+
.. data:: byteorder
An indicator of the native byte order. This will have the value ``'big'`` on
@@ -199,6 +219,10 @@ always available.
installed in :file:`{exec_prefix}/lib/python{X.Y}/lib-dynload`, where *X.Y*
is the version number of Python, for example ``3.2``.
+ .. note:: If a virtual environment is in effect, this value will be changed
+ in ``site.py`` to point to the virtual environment. The value for the
+ Python installation will still be available, via :data:`base_exec_prefix`.
+
.. data:: executable
@@ -775,6 +799,10 @@ always available.
stored in :file:`{prefix}/include/python{X.Y}`, where *X.Y* is the version
number of Python, for example ``3.2``.
+ .. note:: If a virtual environment is in effect, this value will be changed
+ in ``site.py`` to point to the virtual environment. The value for the
+ Python installation will still be available, via :data:`base_prefix`.
+
.. data:: ps1
ps2
diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst
new file mode 100644
index 0000000000..b86f5731e8
--- /dev/null
+++ b/Doc/library/venv.rst
@@ -0,0 +1,193 @@
+:mod:`venv` --- Creation of virtual environments
+================================================
+
+.. module:: venv
+ :synopsis: Creation of virtual environments.
+.. moduleauthor:: Vinay Sajip <vinay_sajip@yahoo.co.uk>
+.. sectionauthor:: Vinay Sajip <vinay_sajip@yahoo.co.uk>
+
+
+.. index:: pair: Environments; virtual
+
+.. versionadded:: 3.3
+
+**Source code:** :source:`Lib/venv.py`
+
+--------------
+
+The :mod:`venv` module provides support for creating lightweight
+"virtual environments" with their own site directories, optionally
+isolated from system site directories. Each virtual environment has
+its own Python binary (allowing creation of environments with various
+Python versions) and can have its own independent set of installed
+Python packages in its site directories.
+
+Creating virtual environments
+-----------------------------
+
+Creation of virtual environments is simplest executing the ``pyvenv``
+script::
+
+ pyvenv /path/to/new/virtual/environment
+
+Running this command creates the target directory (creating any parent
+directories that don't exist already) and places a ``pyvenv.cfg`` file
+in it with a ``home`` key pointing to the Python installation the
+command was run from. It also creates a ``bin`` (or ``Scripts`` on
+Windows) subdirectory containing a copy of the ``python`` binary (or
+binaries, in the case of Windows) and the ``pysetup3`` script (to
+facilitate easy installation of packages from PyPI into the new virtualenv).
+It also creates an (initially empty) ``lib/pythonX.Y/site-packages``
+subdirectory (on Windows, this is ``Lib\site-packages``).
+
+.. highlight:: none
+
+On Windows, you may have to invoke the ``pyvenv`` script as follows, if you
+don't have the relevant PATH and PATHEXT settings::
+
+ c:\Temp>c:\Python33\python c:\Python33\Tools\Scripts\pyvenv.py myenv
+
+or equivalently::
+
+ c:\Temp>c:\Python33\python -m venv myenv
+
+The command, if run with ``-h``, will show the available options::
+
+ usage: pyvenv [-h] [--system-site-packages] [--symlink] [--clear]
+ ENV_DIR [ENV_DIR ...]
+
+ Creates virtual Python environments in one or more target directories.
+
+ positional arguments:
+ ENV_DIR A directory to create the environment in.
+
+ optional arguments:
+ -h, --help show this help message and exit
+ --system-site-packages Give access to the global site-packages dir to the
+ virtual environment.
+ --symlink Attempt to symlink rather than copy.
+ --clear Delete the environment directory if it already exists.
+ If not specified and the directory exists, an error is
+ raised.
+
+
+If the target directory already exists an error will be raised, unless
+the ``--clear`` option was provided, in which case the target
+directory will be deleted and virtual environment creation will
+proceed as usual.
+
+The created ``pyvenv.cfg`` file also includes the
+``include-system-site-packages`` key, set to ``true`` if ``venv`` is
+run with the ``--system-site-packages`` option, ``false`` otherwise.
+
+Multiple paths can be given to ``pyvenv``, in which case an identical
+virtualenv will be created, according to the given options, at each
+provided path.
+
+
+API
+---
+
+The high-level method described above makes use of a simple API which provides
+mechanisms for third-party virtual environment creators to customize
+environment creation according to their needs.
+
+The :class:`EnvBuilder` class accepts the following keyword arguments on
+instantiation:
+
+ * ``system_site_packages`` - A Boolean value indicating that the
+ system Python site-packages should be available to the
+ environment (defaults to ``False``).
+
+ * ``clear`` - A Boolean value which, if True, will delete any
+ existing target directory instead of raising an exception
+ (defaults to ``False``).
+
+ * ``symlinks`` - A Boolean value indicating whether to attempt
+ to symlink the Python binary (and any necessary DLLs or other
+ binaries, e.g. ``pythonw.exe``), rather than copying. Defaults to
+ ``True`` on Linux and Unix systems, but ``False`` on Windows and
+ Mac OS X.
+
+The returned env-builder is an object which has a method, ``create``,
+which takes as required argument the path (absolute or relative to the current
+directory) of the target directory which is to contain the virtual environment.
+The ``create`` method will either create the environment in the specified
+directory, or raise an appropriate exception.
+
+Creators of third-party virtual environment tools will be free to use
+the provided ``EnvBuilder`` class as a base class.
+
+.. highlight:: python
+
+The ``venv`` module will also provide a module-level function as a
+convenience::
+
+ def create(env_dir,
+ system_site_packages=False, clear=False, symlinks=False):
+ builder = EnvBuilder(
+ system_site_packages=system_site_packages,
+ clear=clear,
+ symlinks=symlinks)
+ builder.create(env_dir)
+
+The ``create`` method of the ``EnvBuilder`` class illustrates the
+hooks available for subclass customization::
+
+ def create(self, env_dir):
+ """
+ Create a virtualized Python environment in a directory.
+
+ :param env_dir: The target directory to create an environment in.
+
+ """
+ env_dir = os.path.abspath(env_dir)
+ context = self.create_directories(env_dir)
+ self.create_configuration(context)
+ self.setup_python(context)
+ self.setup_scripts(context)
+ self.post_setup(context)
+
+Each of the methods ``create_directories``, ``create_configuration``,
+``setup_python``, ``setup_scripts`` and ``post_setup`` can be
+overridden. The functions of these methods are:
+
+ * ``create_directories`` - creates the environment directory and
+ all necessary directories, and returns a context object. This is
+ just a holder for attributes (such as paths), for use by the
+ other methods.
+
+ * ``create_configuration`` - creates the ``pyvenv.cfg``
+ configuration file in the environment.
+
+ * ``setup_python`` - creates a copy of the Python executable (and,
+ under Windows, DLLs) in the environment.
+
+ * ``setup_scripts`` - Installs activation scripts appropriate to the
+ platform into the virtual environment.
+
+ * ``post_setup`` - A placeholder method which can be overridden
+ in third party implementations to pre-install packages in the
+ virtual environment or perform other post-creation steps.
+
+In addition, ``EnvBuilder`` provides an ``install_scripts`` utility
+method that can be called from ``setup_scripts`` or ``post_setup`` in
+subclasses to assist in installing custom scripts into the virtual
+environment. The method accepts as arguments the context object (see
+above) and a path to a directory. The directory should contain
+subdirectories "common", "posix", "nt", each containing scripts
+destined for the bin directory in the environment. The contents of
+"common" and the directory corresponding to ``os.name`` are copied
+after some text replacement of placeholders:
+
+* ``__VENV_DIR__`` is replaced with the absolute path of the
+ environment directory.
+
+* ``__VENV_NAME__`` is replaced with the environment name (final path
+ segment of environment directory).
+
+* ``__VENV_BIN_NAME__`` is replaced with the name of the bin directory
+ (either ``bin`` or ``Scripts``).
+
+* ``__VENV_PYTHON__`` is replaced with the absolute path of the
+ environment's executable.
diff --git a/Lib/distutils/sysconfig.py b/Lib/distutils/sysconfig.py
index 16902ca920..977962f748 100644
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -18,6 +18,8 @@ from .errors import DistutilsPlatformError
# These are needed in a couple of spots, so just compute them once.
PREFIX = os.path.normpath(sys.prefix)
EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
+BASE_PREFIX = os.path.normpath(sys.base_prefix)
+BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix)
# Path to the base directory of the project. On Windows the binary may
# live in project/PCBuild9. If we're dealing with an x64 Windows build,
@@ -39,11 +41,18 @@ if os.name == "nt" and "\\pcbuild\\amd64" in project_base[-14:].lower():
# different (hard-wired) directories.
# Setup.local is available for Makefile builds including VPATH builds,
# Setup.dist is available on Windows
-def _python_build():
+def _is_python_source_dir(d):
for fn in ("Setup.dist", "Setup.local"):
- if os.path.isfile(os.path.join(project_base, "Modules", fn)):
+ if os.path.isfile(os.path.join(d, "Modules", fn)):
return True
return False
+_sys_home = getattr(sys, '_home', None)
+if _sys_home and os.name == 'nt' and _sys_home.lower().endswith('pcbuild'):
+ _sys_home = os.path.dirname(_sys_home)
+def _python_build():
+ if _sys_home:
+ return _is_python_source_dir(_sys_home)
+ return _is_python_source_dir(project_base)
python_build = _python_build()
# Calculate the build qualifier flags if they are defined. Adding the flags
@@ -74,11 +83,11 @@ def get_python_inc(plat_specific=0, prefix=None):
otherwise, this is the path to platform-specific header files
(namely pyconfig.h).
- If 'prefix' is supplied, use it instead of sys.prefix or
- sys.exec_prefix -- i.e., ignore 'plat_specific'.
+ If 'prefix' is supplied, use it instead of sys.base_prefix or
+ sys.base_exec_prefix -- i.e., ignore 'plat_specific'.
"""
if prefix is None:
- prefix = plat_specific and EXEC_PREFIX or PREFIX
+ prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX
if os.name == "posix":
if python_build:
# Assume the executable is in the build directory. The
@@ -86,11 +95,12 @@ def get_python_inc(plat_specific=0, prefix=None):
# the build directory may not be the source directory, we
# must use "srcdir" from the makefile to find the "Include"
# directory.
- base = os.path.dirname(os.path.abspath(sys.executable))
+ base = _sys_home or os.path.dirname(os.path.abspath(sys.executable))
if plat_specific:
return base
else:
- incdir = os.path.join(get_config_var('srcdir'), 'Include')
+ incdir = os.path.join(_sys_home or get_config_var('srcdir'),
+ 'Include')
return os.path.normpath(incdir)
python_dir = 'python' + get_python_version() + build_flags
return os.path.join(prefix, "include", python_dir)
@@ -115,11 +125,14 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
containing standard Python library modules; otherwise, return the
directory for site-specific modules.
- If 'prefix' is supplied, use it instead of sys.prefix or
- sys.exec_prefix -- i.e., ignore 'plat_specific'.
+ If 'prefix' is supplied, use it instead of sys.base_prefix or
+ sys.base_exec_prefix -- i.e., ignore 'plat_specific'.
"""
if prefix is None:
- prefix = plat_specific and EXEC_PREFIX or PREFIX
+ if standard_lib:
+ prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX
+ else:
+ prefix = plat_specific and EXEC_PREFIX or PREFIX
if os.name == "posix":
libpython = os.path.join(prefix,
@@ -232,9 +245,9 @@ def get_config_h_filename():
"""Return full pathname of installed pyconfig.h file."""
if python_build:
if os.name == "nt":
- inc_dir = os.path.join(project_base, "PC")
+ inc_dir = os.path.join(_sys_home or project_base, "PC")
else:
- inc_dir = project_base
+ inc_dir = _sys_home or project_base
else:
inc_dir = get_python_inc(plat_specific=1)
if get_python_version() < '2.2':
@@ -248,7 +261,8 @@ def get_config_h_filename():
def get_makefile_filename():
"""Return full pathname of installed Makefile from the Python build."""
if python_build:
- return os.path.join(os.path.dirname(sys.executable), "Makefile")
+ return os.path.join(_sys_home or os.path.dirname(sys.executable),
+ "Makefile")
lib_dir = get_python_lib(plat_specific=0, standard_lib=1)
config_file = 'config-{}{}'.format(get_python_version(), build_flags)
return os.path.join(lib_dir, config_file, 'Makefile')
diff --git a/Lib/gettext.py b/Lib/gettext.py
index 256e331eba..e43f044cc7 100644
--- a/Lib/gettext.py
+++ b/Lib/gettext.py
@@ -55,7 +55,7 @@ __all__ = ['NullTranslations', 'GNUTranslations', 'Catalog',
'dgettext', 'dngettext', 'gettext', 'ngettext',
]
-_default_localedir = os.path.join(sys.prefix, 'share', 'locale')
+_default_localedir = os.path.join(sys.base_prefix, 'share', 'locale')
def c2py(plural):
diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py
index c32064dd31..344f35d84a 100644
--- a/Lib/idlelib/EditorWindow.py
+++ b/Lib/idlelib/EditorWindow.py
@@ -120,7 +120,7 @@ class EditorWindow(object):
def __init__(self, flist=None, filename=None, key=None, root=None):
if EditorWindow.help_url is None:
- dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
+ dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
if sys.platform.count('linux'):
# look for html docs in a couple of standard places
pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
@@ -131,13 +131,13 @@ class EditorWindow(object):
dochome = os.path.join(basepath, pyver,
'Doc', 'index.html')
elif sys.platform[:3] == 'win':
- chmfile = os.path.join(sys.prefix, 'Doc',
+ chmfile = os.path.join(sys.base_prefix, 'Doc',
'Python%s.chm' % _sphinx_version())
if os.path.isfile(chmfile):
dochome = chmfile
elif macosxSupport.runningAsOSXApp():
# documentation is stored inside the python framework
- dochome = os.path.join(sys.prefix,
+ dochome = os.path.join(sys.base_prefix,
'Resources/English.lproj/Documentation/index.html')
dochome = os.path.normpath(dochome)
if os.path.isfile(dochome):
diff --git a/Lib/packaging/command/build_ext.py b/Lib/packaging/command/build_ext.py
index 99cf8ce2e7..7aa0b3a741 100644
--- a/Lib/packaging/command/build_ext.py
+++ b/Lib/packaging/command/build_ext.py
@@ -182,7 +182,10 @@ class build_ext(Command):
# the 'libs' directory is for binary installs - we assume that
# must be the *native* platform. But we don't really support
# cross-compiling via a binary install anyway, so we let it go.
- self.library_dirs.append(os.path.join(sys.exec_prefix, 'libs'))
+ # Note that we must use sys.base_exec_prefix here rather than
+ # exec_prefix, since the Python libs are not copied to a virtual
+ # environment.
+ self.library_dirs.append(os.path.join(sys.base_exec_prefix, 'libs'))
if self.debug:
self.build_temp = os.path.join(self.build_temp, "Debug")
else:
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 942c98d939..8beedc1892 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -369,7 +369,7 @@ class Doc:
docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS)
- basedir = os.path.join(sys.exec_prefix, "lib",
+ basedir = os.path.join(sys.base_exec_prefix, "lib",
"python%d.%d" % sys.version_info[:2])
if (isinstance(object, type(os)) and
(object.__name__ in ('errno', 'exceptions', 'gc', 'imp',
diff --git a/Lib/site.py b/Lib/site.py
index c289f565f9..a298f26a69 100644
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -13,6 +13,19 @@ prefixes directly, as well as with lib/site-packages appended. The
resulting directories, if they exist, are appended to sys.path, and
also inspected for path configuration files.
+If a file named "pyvenv.cfg" exists one directory above sys.executable,
+sys.prefix and sys.exec_prefix are set to that directory and
+it is also checked for site-packages and site-python (sys.prefix and
+sys.exec_prefix will always be the "real" prefixes of the Python
+installation). If "pyvenv.cfg" (a bootstrap configuration file) contains
+the key "include-system-site-packages" set to anything other than "false"
+(case-insensitive), the system-level prefixes will still also be
+searched for site-packages; otherwise they won't.
+
+All of the resulting site-specific directories, if they exist, are
+appended to sys.path, and also inspected for path configuration
+files.
+
A path configuration file is a file whose name has the form
<package>.pth; its contents are additional directories (one per line)
to be added to sys.path. Non-existing directories (or
@@ -54,6 +67,7 @@ ImportError exception, it is silently ignored.
import sys
import os
+import re
import builtins
# Prefixes for site-packages; add additional prefixes like /usr/local here
@@ -179,6 +193,7 @@ def addsitedir(sitedir, known_paths=None):
sitedir, sitedircase = makepath(sitedir)
if not sitedircase in known_paths:
sys.path.append(sitedir) # Add path component
+ known_paths.add(sitedircase)
try:
names = os.listdir(sitedir)
except os.error:
@@ -266,18 +281,21 @@ def addusersitepackages(known_paths):
addsitedir(user_site, known_paths)
return known_paths
-def getsitepackages():
+def getsitepackages(prefixes=None):
"""Returns a list containing all global site-packages directories
(and possibly site-python).
- For each directory present in the global ``PREFIXES``, this function
- will find its `site-packages` subdirectory depending on the system
- environment, and will return a list of full paths.
+ For each directory present in ``prefixes`` (or the global ``PREFIXES``),
+ this function will find its `site-packages` subdirectory depending on the
+ system environment, and will return a list of full paths.
"""
sitepackages = []
seen = set()
- for prefix in PREFIXES:
+ if prefixes is None:
+ prefixes = PREFIXES
+
+ for prefix in prefixes:
if not prefix or prefix in seen:
continue
seen.add(prefix)
@@ -303,9 +321,9 @@ def getsitepackages():
sys.version[:3], "site-packages"))
return sitepackages
-def addsitepackages(known_paths):
+def addsitepackages(known_paths, prefixes=None):
"""Add site-packages (and possibly site-python) to sys.path"""
- for sitedir in getsitepackages():
+ for sitedir in getsitepackages(prefixes):
if os.path.isdir(sitedir):
addsitedir(sitedir, known_paths)
@@ -475,6 +493,61 @@ def aliasmbcs():
encodings.aliases.aliases[enc] = 'mbcs'
+CONFIG_LINE = re.compile(r'^(?P<key>(\w|[-_])+)\s*=\s*(?P<value>.*)\s*$')
+
+def venv(known_paths):
+ global PREFIXES, ENABLE_USER_SITE
+
+ env = os.environ
+ if sys.platform == 'darwin' and '__PYTHONV_LAUNCHER__' in env:
+ executable = os.environ['__PYTHONV_LAUNCHER__']
+ else:
+ executable = sys.executable
+ executable_dir, executable_name = os.path.split(executable)
+ site_prefix = os.path.dirname(executable_dir)
+ sys._home = None
+ if sys.platform == 'win32':
+ executable_name = os.path.splitext(executable_name)[0]
+ conf_basename = 'pyvenv.cfg'
+ candidate_confs = [
+ conffile for conffile in (
+ os.path.join(executable_dir, conf_basename),
+ os.path.join(site_prefix, conf_basename)
+ )
+ if os.path.isfile(conffile)
+ ]
+
+ if candidate_confs:
+ virtual_conf = candidate_confs[0]
+ system_site = "true"
+ with open(virtual_conf) as f:
+ for line in f:
+ line = line.strip()
+ m = CONFIG_LINE.match(line)
+ if m:
+ d = m.groupdict()
+ key, value = d['key'].lower(), d['value']
+ if key == 'include-system-site-packages':
+ system_site = value.lower()
+ elif key == 'home':
+ sys._home = value
+
+ sys.prefix = sys.exec_prefix = site_prefix
+
+ # Doing this here ensures venv takes precedence over user-site
+ addsitepackages(known_paths, [sys.prefix])
+
+ # addsitepackages will process site_prefix again if its in PREFIXES,
+ # but that's ok; known_paths will prevent anything being added twice
+ if system_site == "true":
+ PREFIXES.insert(0, sys.prefix)
+ else:
+ PREFIXES = [sys.prefix]
+ ENABLE_USER_SITE = False
+
+ return known_paths
+
+
def execsitecustomize():
"""Run custom site specific code, if available."""
try:
@@ -517,6 +590,7 @@ def main():
abs_paths()
known_paths = removeduppaths()
+ known_paths = venv(known_paths)
if ENABLE_USER_SITE is None:
ENABLE_USER_SITE = check_enableusersite()
known_paths = addusersitepackages(known_paths)
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 1539891404..553f160405 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -1021,7 +1021,7 @@ class Popen(object):
if not os.path.exists(w9xpopen):
# Eeek - file-not-found - possibly an embedding
# situation - see if we can locate it in sys.exec_prefix
- w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix),
+ w9xpopen = os.path.join(os.path.dirname(sys.base_exec_prefix),
"w9xpopen.exe")
if not os.path.exists(w9xpopen):
raise RuntimeError("Cannot locate w9xpopen.exe, which is "
diff --git a/Lib/sysconfig.cfg b/Lib/sysconfig.cfg
index 565c0ebaec..87fb091570 100644
--- a/Lib/sysconfig.cfg
+++ b/Lib/sysconfig.cfg
@@ -36,41 +36,41 @@ statedir = /var
# User resource directory
local = ~/.local/{distribution.name}
-stdlib = {base}/lib/python{py_version_short}
+stdlib = {installed_base}/lib/python{py_version_short}
platstdlib = {platbase}/lib/python{py_version_short}
purelib = {base}/lib/python{py_version_short}/site-packages
platlib = {platbase}/lib/python{py_version_short}/site-packages
-include = {base}/include/python{py_version_short}{abiflags}
-platinclude = {platbase}/include/python{py_version_short}{abiflags}
+include = {installed_base}/include/python{py_version_short}{abiflags}
+platinclude = {installed_platbase}/include/python{py_version_short}{abiflags}
data = {base}
[posix_home]
-stdlib = {base}/lib/python
+stdlib = {installed_base}/lib/python
platstdlib = {base}/lib/python
purelib = {base}/lib/python
platlib = {base}/lib/python
-include = {base}/include/python
-platinclude = {base}/include/python
+include = {installed_base}/include/python
+platinclude = {installed_base}/include/python
scripts = {base}/bin
data = {base}
[nt]
-stdlib = {base}/Lib
+stdlib = {installed_base}/Lib
platstdlib = {base}/Lib
purelib = {base}/Lib/site-packages
platlib = {base}/Lib/site-packages
-include = {base}/Include
-platinclude = {base}/Include
+include = {installed_base}/Include
+platinclude = {installed_base}/Include
scripts = {base}/Scripts
data = {base}
[os2]
-stdlib = {base}/Lib
+stdlib = {installed_base}/Lib
platstdlib = {base}/Lib
purelib = {base}/Lib/site-packages
platlib = {base}/Lib/site-packages
-include = {base}/Include
-platinclude = {base}/Include
+include = {installed_base}/Include
+platinclude = {installed_base}/Include
scripts = {base}/Scripts
data = {base}
diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py
index e5c1e60d3e..6ed9fd831f 100644
--- a/Lib/sysconfig.py
+++ b/Lib/sysconfig.py
@@ -3,6 +3,7 @@
import os
import re
import sys
+import os
from os.path import pardir, realpath
from configparser import RawConfigParser
@@ -61,13 +62,15 @@ def _expand_globals(config):
_expand_globals(_SCHEMES)
- # FIXME don't rely on sys.version here, its format is an implementatin detail
+ # FIXME don't rely on sys.version here, its format is an implementation detail
# of CPython, use sys.version_info or sys.hexversion
_PY_VERSION = sys.version.split()[0]
_PY_VERSION_SHORT = sys.version[:3]
_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[2]
_PREFIX = os.path.normpath(sys.prefix)
+_BASE_PREFIX = os.path.normpath(sys.base_prefix)
_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
+_BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix)
_CONFIG_VARS = None
_USER_BASE = None
@@ -94,14 +97,22 @@ if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
_PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
-
-def is_python_build():
+def _is_python_source_dir(d):
for fn in ("Setup.dist", "Setup.local"):
- if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
+ if os.path.isfile(os.path.join(d, "Modules", fn)):
return True
return False
-_PYTHON_BUILD = is_python_build()
+_sys_home = getattr(sys, '_home', None)
+if _sys_home and os.name == 'nt' and _sys_home.lower().endswith('pcbuild'):
+ _sys_home = os.path.dirname(_sys_home)
+
+def is_python_build(check_home=False):
+ if check_home and _sys_home:
+ return _is_python_source_dir(_sys_home)
+ return _is_python_source_dir(_PROJECT_BASE)
+
+_PYTHON_BUILD = is_python_build(True)
if _PYTHON_BUILD:
for scheme in ('posix_prefix', 'posix_home'):
@@ -312,7 +323,7 @@ def _parse_makefile(filename, vars=None):
def get_makefile_filename():
"""Return the path of the Makefile."""
if _PYTHON_BUILD:
- return os.path.join(_PROJECT_BASE, "Makefile")
+ return os.path.join(_sys_home or _PROJECT_BASE, "Makefile")
if hasattr(sys, 'abiflags'):
config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags)
else:
@@ -412,9 +423,9 @@ def get_config_h_filename():
"""Return the path of pyconfig.h."""
if _PYTHON_BUILD:
if os.name == "nt":
- inc_dir = os.path.join(_PROJECT_BASE, "PC")
+ inc_dir = os.path.join(_sys_home or _PROJECT_BASE, "PC")
else:
- inc_dir = _PROJECT_BASE
+ inc_dir = _sys_home or _PROJECT_BASE
else:
inc_dir = get_path('platinclude')
return os.path.join(inc_dir, 'pyconfig.h')
@@ -472,7 +483,9 @@ def get_config_vars(*args):
_CONFIG_VARS['py_version'] = _PY_VERSION
_CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
_CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
+ _CONFIG_VARS['installed_base'] = _BASE_PREFIX
_CONFIG_VARS['base'] = _PREFIX
+ _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX
_CONFIG_VARS['platbase'] = _EXEC_PREFIX
_CONFIG_VARS['projectbase'] = _PROJECT_BASE
try:
diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py
index 26066078b6..8161b942bc 100755
--- a/Lib/test/regrtest.py
+++ b/Lib/test/regrtest.py
@@ -564,7 +564,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False,
random.shuffle(selected)
if trace:
import trace, tempfile
- tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,
+ tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,
tempfile.gettempdir()],
trace=False, count=True)
diff --git a/Lib/test/test_cmd.py b/Lib/test/test_cmd.py
index 3a463558fb..6618535823 100644
--- a/Lib/test/test_cmd.py
+++ b/Lib/test/test_cmd.py
@@ -228,7 +228,7 @@ def test_main(verbose=None):
def test_coverage(coverdir):
trace = support.import_module('trace')
- tracer=trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,],
+ tracer=trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
trace=0, count=1)
tracer.run('reload(cmd);test_main()')
r=tracer.results()
diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py
index cdcd389940..44b95546fc 100644
--- a/Lib/test/test_doctest.py
+++ b/Lib/test/test_doctest.py
@@ -2543,7 +2543,7 @@ import sys, re, io
def test_coverage(coverdir):
trace = support.import_module('trace')
- tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,],
+ tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
trace=0, count=1)
tracer.run('test_main()')
r = tracer.results()
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 0f8d1ca959..c21d15b011 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -189,6 +189,8 @@ class ProcessTestCase(BaseTestCase):
p.wait()
self.assertEqual(p.stderr, None)
+ @unittest.skipIf(sys.base_prefix != sys.prefix,
+ 'Test is not venv-compatible')
def test_executable_with_cwd(self):
python_dir = os.path.dirname(os.path.realpath(sys.executable))
p = subprocess.Popen(["somethingyoudonthave", "-c",
@@ -197,6 +199,8 @@ class ProcessTestCase(BaseTestCase):
p.wait()
self.assertEqual(p.returncode, 47)
+ @unittest.skipIf(sys.base_prefix != sys.prefix,
+ 'Test is not venv-compatible')
@unittest.skipIf(sysconfig.is_python_build(),
"need an installed Python. See #7774")
def test_executable_without_cwd(self):
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 71dbd2967c..e3629ffc7c 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -419,6 +419,7 @@ class SysModuleTest(unittest.TestCase):
self.assertIsInstance(sys.builtin_module_names, tuple)
self.assertIsInstance(sys.copyright, str)
self.assertIsInstance(sys.exec_prefix, str)
+ self.assertIsInstance(sys.base_exec_prefix, str)
self.assertIsInstance(sys.executable, str)
self.assertEqual(len(sys.float_info), 11)
self.assertEqual(sys.float_info.radix, 2)
@@ -450,6 +451,7 @@ class SysModuleTest(unittest.TestCase):
self.assertEqual(sys.maxunicode, 0x10FFFF)
self.assertIsInstance(sys.platform, str)
self.assertIsInstance(sys.prefix, str)
+ self.assertIsInstance(sys.base_prefix, str)
self.assertIsInstance(sys.version, str)
vi = sys.version_info
self.assertIsInstance(vi[:], tuple)
@@ -541,6 +543,8 @@ class SysModuleTest(unittest.TestCase):
out = p.communicate()[0].strip()
self.assertEqual(out, b'?')
+ @unittest.skipIf(sys.base_prefix != sys.prefix,
+ 'Test is not venv-compatible')
def test_executable(self):
# sys.executable should be absolute
self.assertEqual(os.path.abspath(sys.executable), sys.executable)
diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py
index a2e6fbcfd6..e583793c44 100644
--- a/Lib/test/test_sysconfig.py
+++ b/Lib/test/test_sysconfig.py
@@ -260,12 +260,17 @@ class TestSysConfig(unittest.TestCase):
# the global scheme mirrors the distinction between prefix and
# exec-prefix but not the user scheme, so we have to adapt the paths
# before comparing (issue #9100)
- adapt = sys.prefix != sys.exec_prefix
+ adapt = sys.base_prefix != sys.base_exec_prefix
for name in ('stdlib', 'platstdlib', 'purelib', 'platlib'):
global_path = get_path(name, 'posix_prefix')
if adapt:
- global_path = global_path.replace(sys.exec_prefix, sys.prefix)
- base = base.replace(sys.exec_prefix, sys.prefix)
+ global_path = global_path.replace(sys.exec_prefix, sys.base_prefix)
+ base = base.replace(sys.exec_prefix, sys.base_prefix)
+ elif sys.base_prefix != sys.prefix:
+ # virtual environment? Likewise, we have to adapt the paths
+ # before comparing
+ global_path = global_path.replace(sys.base_prefix, sys.prefix)
+ base = base.replace(sys.base_prefix, sys.prefix)
user_path = get_path(name, 'posix_user')
self.assertEqual(user_path, global_path.replace(base, user, 1))
diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py
index fa0d48ca6d..ac3a1a30da 100644
--- a/Lib/test/test_trace.py
+++ b/Lib/test/test_trace.py
@@ -316,8 +316,8 @@ class TestCoverage(unittest.TestCase):
# Ignore all files, nothing should be traced nor printed
libpath = os.path.normpath(os.path.dirname(os.__file__))
# sys.prefix does not work when running from a checkout
- tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix, libpath],
- trace=0, count=1)
+ tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,
+ libpath], trace=0, count=1)
with captured_stdout() as stdout:
self._coverage(tracer)
if os.path.exists(TESTFN):
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
new file mode 100644
index 0000000000..fae62ed292
--- /dev/null
+++ b/Lib/test/test_venv.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+#
+# Copyright 2011 by Vinay Sajip. All Rights Reserved.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation, and that the name of Vinay Sajip
+# not be used in advertising or publicity pertaining to distribution
+# of the software without specific, written prior permission.
+# VINAY SAJIP DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# VINAY SAJIP BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+# IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""Test harness for the venv module. Run all tests.
+
+Copyright (C) 2011 Vinay Sajip. All Rights Reserved.
+"""
+
+import os
+import os.path
+import shutil
+import sys
+import tempfile
+from test.support import (captured_stdout, captured_stderr, run_unittest,
+ can_symlink)
+import unittest
+import venv
+
+class BaseTest(unittest.TestCase):
+ """Base class for venv tests."""
+
+ def setUp(self):
+ self.env_dir = tempfile.mkdtemp()
+ if os.name == 'nt':
+ self.bindir = 'Scripts'
+ self.ps3name = 'pysetup3-script.py'
+ self.lib = ('Lib',)
+ self.include = 'Include'
+ self.exe = 'python.exe'
+ else:
+ self.bindir = 'bin'
+ self.ps3name = 'pysetup3'
+ self.lib = ('lib', 'python%s' % sys.version[:3])
+ self.include = 'include'
+ self.exe = 'python'
+
+ def tearDown(self):
+ shutil.rmtree(self.env_dir)
+
+ def run_with_capture(self, func, *args, **kwargs):
+ with captured_stdout() as output:
+ with captured_stderr() as error:
+ func(*args, **kwargs)
+ return output.getvalue(), error.getvalue()
+
+ def get_env_file(self, *args):
+ return os.path.join(self.env_dir, *args)
+
+ def get_text_file_contents(self, *args):
+ with open(self.get_env_file(*args), 'r') as f:
+ result = f.read()
+ return result
+
+class BasicTest(BaseTest):
+ """Test venv module functionality."""
+
+ def test_defaults(self):
+ """
+ Test the create function with default arguments.
+ """
+ def isdir(*args):
+ fn = self.get_env_file(*args)
+ self.assertTrue(os.path.isdir(fn))
+
+ shutil.rmtree(self.env_dir)
+ self.run_with_capture(venv.create, self.env_dir)
+ isdir(self.bindir)
+ isdir(self.include)
+ isdir(*self.lib)
+ data = self.get_text_file_contents('pyvenv.cfg')
+ if sys.platform == 'darwin' and ('__PYTHONV_LAUNCHER__'
+ in os.environ):
+ executable = os.environ['__PYTHONV_LAUNCHER__']
+ else:
+ executable = sys.executable
+ path = os.path.dirname(executable)
+ self.assertIn('home = %s' % path, data)
+ data = self.get_text_file_contents(self.bindir, self.ps3name)
+ self.assertTrue(data.startswith('#!%s%s' % (self.env_dir, os.sep)))
+ fn = self.get_env_file(self.bindir, self.exe)
+ self.assertTrue(os.path.exists(fn))
+
+ def test_overwrite_existing(self):
+ """
+ Test control of overwriting an existing environment directory.
+ """
+ self.assertRaises(ValueError, venv.create, self.env_dir)
+ builder = venv.EnvBuilder(clear=True)
+ builder.create(self.env_dir)
+
+ def test_isolation(self):
+ """
+ Test isolation from system site-packages
+ """
+ for ssp, s in ((True, 'true'), (False, 'false')):
+ builder = venv.EnvBuilder(clear=True, system_site_packages=ssp)
+ builder.create(self.env_dir)
+ data = self.get_text_file_contents('pyvenv.cfg')
+ self.assertIn('include-system-site-packages = %s\n' % s, data)
+
+ @unittest.skipUnless(can_symlink(), 'Needs symlinks')
+ def test_symlinking(self):
+ """
+ Test symlinking works as expected
+ """
+ for usl in (False, True):
+ builder = venv.EnvBuilder(clear=True, symlinks=usl)
+ if (usl and sys.platform == 'darwin' and
+ '__PYTHONV_LAUNCHER__' in os.environ):
+ self.assertRaises(ValueError, builder.create, self.env_dir)
+ else:
+ builder.create(self.env_dir)
+ fn = self.get_env_file(self.bindir, self.exe)
+ # Don't test when False, because e.g. 'python' is always
+ # symlinked to 'python3.3' in the env, even when symlinking in
+ # general isn't wanted.
+ if usl:
+ self.assertTrue(os.path.islink(fn))
+
+def test_main():
+ run_unittest(BasicTest)
+
+if __name__ == "__main__":
+ test_main()
diff --git a/Lib/tkinter/_fix.py b/Lib/tkinter/_fix.py
index 5a69d89787..5f32d25abc 100644
--- a/Lib/tkinter/_fix.py
+++ b/Lib/tkinter/_fix.py
@@ -46,10 +46,10 @@ else:
s = "\\" + s[3:]
return s
-prefix = os.path.join(sys.prefix,"tcl")
+prefix = os.path.join(sys.base_prefix,"tcl")
if not os.path.exists(prefix):
# devdir/../tcltk/lib
- prefix = os.path.join(sys.prefix, os.path.pardir, "tcltk", "lib")
+ prefix = os.path.join(sys.base_prefix, os.path.pardir, "tcltk", "lib")
prefix = os.path.abspath(prefix)
# if this does not exist, no further search is needed
if os.path.exists(prefix):
diff --git a/Lib/trace.py b/Lib/trace.py
index 885824aff2..c0ea090645 100644
--- a/Lib/trace.py
+++ b/Lib/trace.py
@@ -39,8 +39,8 @@ Sample use, programmatically
# create a Trace object, telling it what to ignore, and whether to
# do tracing or line-counting or both.
- tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix,], trace=0,
- count=1)
+ tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
+ trace=0, count=1)
# run the new command using the given tracer
tracer.run('main()')
# make a report, placing output in /tmp
@@ -749,10 +749,10 @@ def main(argv=None):
# should I also call expanduser? (after all, could use $HOME)
s = s.replace("$prefix",
- os.path.join(sys.prefix, "lib",
+ os.path.join(sys.base_prefix, "lib",
"python" + sys.version[:3]))
s = s.replace("$exec_prefix",
- os.path.join(sys.exec_prefix, "lib",
+ os.path.join(sys.base_exec_prefix, "lib",
"python" + sys.version[:3]))
s = os.path.normpath(s)
ignore_dirs.append(s)
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
new file mode 100644
index 0000000000..8c26fb175a
--- /dev/null
+++ b/Lib/venv/__init__.py
@@ -0,0 +1,502 @@
+# Copyright (C) 2011-2012 Vinay Sajip.
+#
+# Use with a Python executable built from the Python fork at
+#
+# https://bitbucket.org/vinay.sajip/pythonv/ as follows:
+#
+# python -m venv env_dir
+#
+# You'll need an Internet connection (needed to download distribute_setup.py).
+#
+# The script will change to the environment's binary directory and run
+#
+# ./python distribute_setup.py
+#
+# after which you can change to the environment's directory and do some
+# installations, e.g.
+#
+# source bin/activate.sh
+# pysetup3 install setuptools-git
+# pysetup3 install Pygments
+# pysetup3 install Jinja2
+# pysetup3 install SQLAlchemy
+# pysetup3 install coverage
+#
+# Note that on Windows, distributions which include C extensions (e.g. coverage)
+# may fail due to lack of a suitable C compiler.
+#
+import base64
+import io
+import logging
+import os
+import os.path
+import shutil
+import sys
+import zipfile
+
+logger = logging.getLogger(__name__)
+
+class Context:
+ """
+ Holds information about a current virtualisation request.
+ """
+ pass
+
+
+class EnvBuilder:
+ """
+ This class exists to allow virtual environment creation to be
+ customised. The constructor parameters determine the builder's
+ behaviour when called upon to create a virtual environment.
+
+ By default, the builder makes the system (global) site-packages dir
+ available to the created environment.
+
+ By default, the creation process uses symlinks wherever possible.
+
+ :param system_site_packages: If True, the system (global) site-packages
+ dir is available to created environments.
+ :param clear: If True and the target directory exists, it is deleted.
+ Otherwise, if the target directory exists, an error is
+ raised.
+ :param symlinks: If True, attempt to symlink rather than copy files into
+ virtual environment.
+ :param upgrade: If True, upgrade an existing virtual environment.
+ """
+
+ def __init__(self, system_site_packages=False, clear=False,
+ symlinks=False, upgrade=False):
+ self.system_site_packages = system_site_packages
+ self.clear = clear
+ self.symlinks = symlinks
+ self.upgrade = upgrade
+
+ def create(self, env_dir):
+ """
+ Create a virtual environment in a directory.
+
+ :param env_dir: The target directory to create an environment in.
+
+ """
+ if (self.symlinks and
+ sys.platform == 'darwin' and
+ 'Library/Framework' in sys.base_prefix):
+ # Symlinking the stub executable in an OSX framework build will
+ # result in a broken virtual environment.
+ raise ValueError(
+ "Symlinking is not supported on OSX framework Python.")
+ env_dir = os.path.abspath(env_dir)
+ context = self.ensure_directories(env_dir)
+ self.create_configuration(context)
+ self.setup_python(context)
+ if not self.upgrade:
+ self.setup_scripts(context)
+ self.post_setup(context)
+
+ def ensure_directories(self, env_dir):
+ """
+ Create the directories for the environment.
+
+ Returns a context object which holds paths in the environment,
+ for use by subsequent logic.
+ """
+
+ def create_if_needed(d):
+ if not os.path.exists(d):
+ os.makedirs(d)
+
+ if os.path.exists(env_dir) and not (self.clear or self.upgrade):
+ raise ValueError('Directory exists: %s' % env_dir)
+ if os.path.exists(env_dir) and self.clear:
+ shutil.rmtree(env_dir)
+ context = Context()
+ context.env_dir = env_dir
+ context.env_name = os.path.split(env_dir)[1]
+ context.prompt = '(%s) ' % context.env_name
+ create_if_needed(env_dir)
+ env = os.environ
+ if sys.platform == 'darwin' and '__PYTHONV_LAUNCHER__' in env:
+ executable = os.environ['__PYTHONV_LAUNCHER__']
+ else:
+ executable = sys.executable
+ dirname, exename = os.path.split(os.path.abspath(executable))
+ context.executable = executable
+ context.python_dir = dirname
+ context.python_exe = exename
+ if sys.platform == 'win32':
+ binname = 'Scripts'
+ incpath = 'Include'
+ libpath = os.path.join(env_dir, 'Lib', 'site-packages')
+ else:
+ binname = 'bin'
+ incpath = 'include'
+ libpath = os.path.join(env_dir, 'lib', 'python%d.%d' % sys.version_info[:2], 'site-packages')
+ context.inc_path = path = os.path.join(env_dir, incpath)
+ create_if_needed(path)
+ create_if_needed(libpath)
+ context.bin_path = binpath = os.path.join(env_dir, binname)
+ context.bin_name = binname
+ context.env_exe = os.path.join(binpath, exename)
+ create_if_needed(binpath)
+ return context
+
+ def create_configuration(self, context):
+ """
+ Create a configuration file indicating where the environment's Python
+ was copied from, and whether the system site-packages should be made
+ available in the environment.
+
+ :param context: The information for the environment creation request
+ being processed.
+ """
+ context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg')
+ with open(path, 'w', encoding='utf-8') as f:
+ f.write('home = %s\n' % context.python_dir)
+ if self.system_site_packages:
+ incl = 'true'
+ else:
+ incl = 'false'
+ f.write('include-system-site-packages = %s\n' % incl)
+ f.write('version = %d.%d.%d\n' % sys.version_info[:3])
+
+ if os.name == 'nt':
+ def include_binary(self, f):
+ if f.endswith(('.pyd', '.dll')):
+ result = True
+ else:
+ result = f.startswith('python') and f.endswith('.exe')
+ return result
+
+ def symlink_or_copy(self, src, dst):
+ """
+ Try symlinking a file, and if that fails, fall back to copying.
+ """
+ force_copy = not self.symlinks
+ if not force_copy:
+ try:
+ if not os.path.islink(dst): # can't link to itself!
+ os.symlink(src, dst)
+ except Exception: # may need to use a more specific exception
+ logger.warning('Unable to symlink %r to %r', src, dst)
+ force_copy = True
+ if force_copy:
+ shutil.copyfile(src, dst)
+
+ def setup_python(self, context):
+ """
+ Set up a Python executable in the environment.
+
+ :param context: The information for the environment creation request
+ being processed.
+ """
+ binpath = context.bin_path
+ exename = context.python_exe
+ path = context.env_exe
+ copier = self.symlink_or_copy
+ copier(context.executable, path)
+ dirname = context.python_dir
+ if os.name != 'nt':
+ if not os.path.islink(path):
+ os.chmod(path, 0o755)
+ path = os.path.join(binpath, 'python')
+ if not os.path.exists(path):
+ os.symlink(exename, path)
+ else:
+ subdir = 'DLLs'
+ include = self.include_binary
+ files = [f for f in os.listdir(dirname) if include(f)]
+ for f in files:
+ src = os.path.join(dirname, f)
+ dst = os.path.join(binpath, f)
+ if dst != context.env_exe: # already done, above
+ copier(src, dst)
+ dirname = os.path.join(dirname, subdir)
+ if os.path.isdir(dirname):
+ files = [f for f in os.listdir(dirname) if include(f)]
+ for f in files:
+ src = os.path.join(dirname, f)
+ dst = os.path.join(binpath, f)
+ copier(src, dst)
+ # copy init.tcl over
+ for root, dirs, files in os.walk(context.python_dir):
+ if 'init.tcl' in files:
+ tcldir = os.path.basename(root)
+ tcldir = os.path.join(context.env_dir, 'Lib', tcldir)
+ os.makedirs(tcldir)
+ src = os.path.join(root, 'init.tcl')
+ dst = os.path.join(tcldir, 'init.tcl')
+ shutil.copyfile(src, dst)
+ break
+
+ def setup_scripts(self, context):
+ """
+ Set up scripts into the created environment from a directory.
+
+ This method installs the default scripts into the environment
+ being created. You can prevent the default installation by overriding
+ this method if you really need to, or if you need to specify
+ a different location for the scripts to install. By default, the
+ 'scripts' directory in the venv package is used as the source of
+ scripts to install.
+ """
+ path = os.path.abspath(os.path.dirname(__file__))
+ path = os.path.join(path, 'scripts')
+ self.install_scripts(context, path)
+
+ def post_setup(self, context):
+ """
+ Hook for post-setup modification of the venv. Subclasses may install
+ additional packages or scripts here, add activation shell scripts, etc.
+
+ :param context: The information for the environment creation request
+ being processed.
+ """
+ pass
+
+ def replace_variables(self, text, context):
+ """
+ Replace variable placeholders in script text with context-specific
+ variables.
+
+ Return the text passed in , but with variables replaced.
+
+ :param text: The text in which to replace placeholder variables.
+ :param context: The information for the environment creation request
+ being processed.
+ """
+ text = text.replace('__VENV_DIR__', context.env_dir)
+ text = text.replace('__VENV_NAME__', context.prompt)
+ text = text.replace('__VENV_BIN_NAME__', context.bin_name)
+ text = text.replace('__VENV_PYTHON__', context.env_exe)
+ return text
+
+ def install_scripts(self, context, path):
+ """
+ Install scripts into the created environment from a directory.
+
+ :param context: The information for the environment creation request
+ being processed.
+ :param path: Absolute pathname of a directory containing script.
+ Scripts in the 'common' subdirectory of this directory,
+ and those in the directory named for the platform
+ being run on, are installed in the created environment.
+ Placeholder variables are replaced with environment-
+ specific values.
+ """
+ binpath = context.bin_path
+ plen = len(path)
+ for root, dirs, files in os.walk(path):
+ if root == path: # at top-level, remove irrelevant dirs
+ for d in dirs[:]:
+ if d not in ('common', os.name):
+ dirs.remove(d)
+ continue # ignore files in top level
+ for f in files:
+ srcfile = os.path.join(root, f)
+ suffix = root[plen:].split(os.sep)[2:]
+ if not suffix:
+ dstdir = binpath
+ else:
+ dstdir = os.path.join(binpath, *suffix)
+ if not os.path.exists(dstdir):
+ os.makedirs(dstdir)
+ dstfile = os.path.join(dstdir, f)
+ with open(srcfile, 'rb') as f:
+ data = f.read()
+ if srcfile.endswith('.exe'):
+ mode = 'wb'
+ else:
+ mode = 'w'
+ data = data.decode('utf-8')
+ data = self.replace_variables(data, context)
+ with open(dstfile, mode) as f:
+ f.write(data)
+ os.chmod(dstfile, 0o755)
+
+
+# This class will not be included in Python core; it's here for now to
+# facilitate experimentation and testing, and as proof-of-concept of what could
+# be done by external extension tools.
+class DistributeEnvBuilder(EnvBuilder):
+ """
+ By default, this builder installs Distribute so that you can pip or
+ easy_install other packages into the created environment.
+
+ :param nodist: If True, Distribute is not installed into the created
+ environment.
+ :param progress: If Distribute is installed, the progress of the
+ installation can be monitored by passing a progress
+ callable. If specified, it is called with two
+ arguments: a string indicating some progress, and a
+ context indicating where the string is coming from.
+ The context argument can have one of three values:
+ 'main', indicating that it is called from virtualize()
+ itself, and 'stdout' and 'stderr', which are obtained
+ by reading lines from the output streams of a subprocess
+ which is used to install Distribute.
+
+ If a callable is not specified, default progress
+ information is output to sys.stderr.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.nodist = kwargs.pop("nodist", False)
+ self.progress = kwargs.pop("progress", None)
+ super().__init__(*args, **kwargs)
+
+ def post_setup(self, context):
+ """
+ Set up any packages which need to be pre-installed into the
+ environment being created.
+
+ :param context: The information for the environment creation request
+ being processed.
+ """
+ if not self.nodist:
+ self.install_distribute(context)
+
+ def reader(self, stream, context):
+ """
+ Read lines from a subprocess' output stream and either pass to a progress
+ callable (if specified) or write progress information to sys.stderr.
+ """
+ progress = self.progress
+ while True:
+ s = stream.readline()
+ if not s:
+ break
+ if progress is not None:
+ progress(s, context)
+ else:
+ sys.stderr.write('.')
+ #sys.stderr.write(s.decode('utf-8'))
+ sys.stderr.flush()
+ stream.close()
+
+ def install_distribute(self, context):
+ """
+ Install Distribute in the environment.
+
+ :param context: The information for the environment creation request
+ being processed.
+ """
+ from subprocess import Popen, PIPE
+ from threading import Thread
+ from urllib.request import urlretrieve
+
+ url = 'http://python-distribute.org/distribute_setup.py'
+ binpath = context.bin_path
+ distpath = os.path.join(binpath, 'distribute_setup.py')
+ # Download Distribute in the env
+ urlretrieve(url, distpath)
+ progress = self.progress
+ if progress is not None:
+ progress('Installing distribute', 'main')
+ else:
+ sys.stderr.write('Installing distribute ')
+ sys.stderr.flush()
+ # Install Distribute in the env
+ args = [context.env_exe, 'distribute_setup.py']
+ p = Popen(args, stdout=PIPE, stderr=PIPE, cwd=binpath)
+ t1 = Thread(target=self.reader, args=(p.stdout, 'stdout'))
+ t1.start()
+ t2 = Thread(target=self.reader, args=(p.stderr, 'stderr'))
+ t2.start()
+ p.wait()
+ t1.join()
+ t2.join()
+ if progress is not None:
+ progress('done.', 'main')
+ else:
+ sys.stderr.write('done.\n')
+ # Clean up - no longer needed
+ os.unlink(distpath)
+
+def create(env_dir, system_site_packages=False, clear=False, symlinks=False):
+ """
+ Create a virtual environment in a directory.
+
+ By default, makes the system (global) site-packages dir available to
+ the created environment.
+
+ :param env_dir: The target directory to create an environment in.
+ :param system_site_packages: If True, the system (global) site-packages
+ dir is available to the environment.
+ :param clear: If True and the target directory exists, it is deleted.
+ Otherwise, if the target directory exists, an error is
+ raised.
+ :param symlinks: If True, attempt to symlink rather than copy files into
+ virtual environment.
+ """
+ # XXX This should be changed to EnvBuilder.
+ builder = DistributeEnvBuilder(system_site_packages=system_site_packages,
+ clear=clear, symlinks=symlinks)
+ builder.create(env_dir)
+
+def main(args=None):
+ compatible = True
+ if sys.version_info < (3, 3):
+ compatible = False
+ elif not hasattr(sys, 'base_prefix'):
+ compatible = False
+ if not compatible:
+ raise ValueError('This script is only for use with '
+ 'Python 3.3 (pythonv variant)')
+ else:
+ import argparse
+
+ parser = argparse.ArgumentParser(prog=__name__,
+ description='Creates virtual Python '
+ 'environments in one or '
+ 'more target '
+ 'directories.')
+ parser.add_argument('dirs', metavar='ENV_DIR', nargs='+',
+ help='A directory to create the environment in.')
+ # XXX This option will be removed.
+ parser.add_argument('--no-distribute', default=False,
+ action='store_true', dest='nodist',
+ help="Don't install Distribute in the virtual "
+ "environment.")
+ 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 os.name == 'nt' or (sys.platform == 'darwin' and
+ 'Library/Framework' in sys.base_prefix):
+ use_symlinks = False
+ else:
+ use_symlinks = True
+ parser.add_argument('--symlinks', default=use_symlinks,
+ action='store_true', dest='symlinks',
+ help="Attempt to symlink rather than copy.")
+ parser.add_argument('--clear', default=False, action='store_true',
+ dest='clear', help='Delete the environment '
+ 'directory if it already '
+ 'exists. If not specified and '
+ 'the directory exists, an error'
+ ' is raised.')
+ parser.add_argument('--upgrade', default=False, action='store_true',
+ dest='upgrade', help='Upgrade the environment '
+ 'directory to use this version '
+ 'of Python, assuming it has been '
+ 'upgraded in-place.')
+ options = parser.parse_args(args)
+ if options.upgrade and options.clear:
+ raise ValueError('you cannot supply --upgrade and --clear together.')
+ # XXX This will be changed to EnvBuilder
+ builder = DistributeEnvBuilder(system_site_packages=options.system_site,
+ clear=options.clear,
+ symlinks=options.symlinks,
+ upgrade=options.upgrade,
+ nodist=options.nodist)
+ for d in options.dirs:
+ builder.create(d)
+
+if __name__ == '__main__':
+ rc = 1
+ try:
+ main()
+ rc = 0
+ except Exception as e:
+ print('Error: %s' % e, file=sys.stderr)
+ sys.exit(rc)
diff --git a/Lib/venv/__main__.py b/Lib/venv/__main__.py
new file mode 100644
index 0000000000..912423e4a7
--- /dev/null
+++ b/Lib/venv/__main__.py
@@ -0,0 +1,10 @@
+import sys
+from . import main
+
+rc = 1
+try:
+ main()
+ rc = 0
+except Exception as e:
+ print('Error: %s' % e, file=sys.stderr)
+sys.exit(rc)
diff --git a/Lib/venv/scripts/nt/Activate.ps1 b/Lib/venv/scripts/nt/Activate.ps1
new file mode 100644
index 0000000000..967ba5c642
--- /dev/null
+++ b/Lib/venv/scripts/nt/Activate.ps1
@@ -0,0 +1,34 @@
+$env:VIRTUAL_ENV="__VENV_DIR__"
+
+# Revert to original values
+if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
+ copy-item function:_OLD_VIRTUAL_PROMPT function:prompt
+ remove-item function:_OLD_VIRTUAL_PROMPT
+}
+
+if (Test-Path env:_OLD_VIRTUAL_PYTHONHOME) {
+ copy-item env:_OLD_VIRTUAL_PYTHONHOME env:PYTHONHOME
+ remove-item env:_OLD_VIRTUAL_PYTHONHOME
+}
+
+if (Test-Path env:_OLD_VIRTUAL_PATH) {
+ copy-item env:_OLD_VIRTUAL_PATH env:PATH
+ remove-item env:_OLD_VIRTUAL_PATH
+}
+
+# Set the prompt to include the env name
+copy-item function:prompt function:_OLD_VIRTUAL_PROMPT
+function prompt {
+ Write-Host -NoNewline -ForegroundColor Green [__VENV_NAME__]
+ _OLD_VIRTUAL_PROMPT
+}
+
+# Clear PYTHONHOME
+if (Test-Path env:PYTHONHOME) {
+ copy-item env:PYTHONHOME env:_OLD_VIRTUAL_PYTHONHOME
+ remove-item env:PYTHONHOME
+}
+
+# Add the venv to the PATH
+copy-item env:PATH env:_OLD_VIRTUAL_PATH
+$env:PATH = "$env:VIRTUAL_ENV\__VENV_BIN_NAME__;$env:PATH"
diff --git a/Lib/venv/scripts/nt/Deactivate.ps1 b/Lib/venv/scripts/nt/Deactivate.ps1
new file mode 100644
index 0000000000..3d1e96bc8c
--- /dev/null
+++ b/Lib/venv/scripts/nt/Deactivate.ps1
@@ -0,0 +1,19 @@
+# Revert to original values
+if (Test-Path function:_OLD_VIRTUAL_PROMPT) {
+ copy-item function:_OLD_VIRTUAL_PROMPT function:prompt
+ remove-item function:_OLD_VIRTUAL_PROMPT
+}
+
+if (Test-Path env:_OLD_VIRTUAL_PYTHONHOME) {
+ copy-item env:_OLD_VIRTUAL_PYTHONHOME env:PYTHONHOME
+ remove-item env:_OLD_VIRTUAL_PYTHONHOME
+}
+
+if (Test-Path env:_OLD_VIRTUAL_PATH) {
+ copy-item env:_OLD_VIRTUAL_PATH env:PATH
+ remove-item env:_OLD_VIRTUAL_PATH
+}
+
+if (Test-Path env:VIRTUAL_ENV) {
+ remove-item env:VIRTUAL_ENV
+}
diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat
new file mode 100644
index 0000000000..c45e65a26f
--- /dev/null
+++ b/Lib/venv/scripts/nt/activate.bat
@@ -0,0 +1,31 @@
+@echo off
+set VIRTUAL_ENV=__VENV_DIR__
+
+if not defined PROMPT (
+ set PROMPT=$P$G
+)
+
+if defined _OLD_VIRTUAL_PROMPT (
+ set PROMPT=%_OLD_VIRTUAL_PROMPT%
+)
+
+if defined _OLD_VIRTUAL_PYTHONHOME (
+ set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%
+)
+
+set _OLD_VIRTUAL_PROMPT=%PROMPT%
+set PROMPT=__VENV_NAME__%PROMPT%
+
+if defined PYTHONHOME (
+ set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%
+ set PYTHONHOME=
+)
+
+if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%; goto SKIPPATH
+
+set _OLD_VIRTUAL_PATH=%PATH%
+
+:SKIPPATH
+set PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH%
+
+:END
diff --git a/Lib/venv/scripts/nt/deactivate.bat b/Lib/venv/scripts/nt/deactivate.bat
new file mode 100644
index 0000000000..62da5b1308
--- /dev/null
+++ b/Lib/venv/scripts/nt/deactivate.bat
@@ -0,0 +1,17 @@
+@echo off
+
+if defined _OLD_VIRTUAL_PROMPT (
+ set PROMPT=%_OLD_VIRTUAL_PROMPT%
+)
+set _OLD_VIRTUAL_PROMPT=
+
+if defined _OLD_VIRTUAL_PYTHONHOME (
+ set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%
+ set _OLD_VIRTUAL_PYTHONHOME=
+)
+
+if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%
+
+set _OLD_VIRTUAL_PATH=
+
+:END
diff --git a/Lib/venv/scripts/nt/pysetup3-script.py b/Lib/venv/scripts/nt/pysetup3-script.py
new file mode 100644
index 0000000000..cfc6661d67
--- /dev/null
+++ b/Lib/venv/scripts/nt/pysetup3-script.py
@@ -0,0 +1,11 @@
+#!__VENV_PYTHON__
+if __name__ == '__main__':
+ rc = 1
+ try:
+ import sys, re, packaging.run
+ sys.argv[0] = re.sub('-script.pyw?$', '', sys.argv[0])
+ rc = packaging.run.main() # None interpreted as 0
+ except Exception:
+ # use syntax which works with either 2.x or 3.x
+ sys.stderr.write('%s\n' % sys.exc_info()[1])
+ sys.exit(rc)
diff --git a/Lib/venv/scripts/nt/pysetup3.exe b/Lib/venv/scripts/nt/pysetup3.exe
new file mode 100644
index 0000000000..3f3c09ebc8
--- /dev/null
+++ b/Lib/venv/scripts/nt/pysetup3.exe
Binary files differ
diff --git a/Lib/venv/scripts/posix/activate b/Lib/venv/scripts/posix/activate
new file mode 100644
index 0000000000..c241450c0b
--- /dev/null
+++ b/Lib/venv/scripts/posix/activate
@@ -0,0 +1,76 @@
+# This file must be used with "source bin/activate" *from bash*
+# you cannot run it directly
+
+deactivate () {
+ # 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
+
+ # This should detect bash and zsh, which have a hash command that must
+ # be called to get it to forget past commands. Without forgetting
+ # past commands the $PATH changes we made may not be respected
+ if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
+ hash -r
+ fi
+
+ if [ -n "$_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 irrelavent variables
+deactivate nondestructive
+
+VIRTUAL_ENV="__VENV_DIR__"
+export VIRTUAL_ENV
+
+_OLD_VIRTUAL_PATH="$PATH"
+PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
+export PATH
+
+# unset PYTHONHOME if set
+# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
+# could use `if (set -u; : $PYTHONHOME) ;` in bash
+if [ -n "$PYTHONHOME" ] ; then
+ _OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
+ unset PYTHONHOME
+fi
+
+if [ -z "$VIRTUAL_ENV_DISABLE_PROMPT" ] ; then
+ _OLD_VIRTUAL_PS1="$PS1"
+ if [ "x__VENV_NAME__" != x ] ; then
+ PS1="__VENV_NAME__$PS1"
+ else
+ if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
+ # special case for Aspen magic directories
+ # see http://www.zetadev.com/software/aspen/
+ PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
+ else
+ PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
+ fi
+ fi
+ export PS1
+fi
+
+# This should detect bash and zsh, which have a hash command that must
+# be called to get it to forget past commands. Without forgetting
+# past commands the $PATH changes we made may not be respected
+if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
+ hash -r
+fi
diff --git a/Lib/venv/scripts/posix/pysetup3 b/Lib/venv/scripts/posix/pysetup3
new file mode 100644
index 0000000000..900f50e837
--- /dev/null
+++ b/Lib/venv/scripts/posix/pysetup3
@@ -0,0 +1,11 @@
+#!__VENV_PYTHON__
+if __name__ == '__main__':
+ rc = 1
+ try:
+ import sys, re, packaging.run
+ sys.argv[0] = re.sub('-script.pyw?$', '', sys.argv[0])
+ rc = packaging.run.main() # None interpreted as 0
+ except Exception:
+ # use syntax which works with either 2.x or 3.x
+ sys.stderr.write('%s\n' % sys.exc_info()[1])
+ sys.exit(rc)
diff --git a/Mac/Makefile.in b/Mac/Makefile.in
index 8a62a9097c..6d2ad164c6 100644
--- a/Mac/Makefile.in
+++ b/Mac/Makefile.in
@@ -72,7 +72,7 @@ installunixtools:
for fn in python3 pythonw3 idle3 pydoc3 python3-config \
python$(VERSION) pythonw$(VERSION) idle$(VERSION) \
pydoc$(VERSION) python$(VERSION)-config 2to3 \
- 2to3-$(VERSION) ;\
+ 2to3-$(VERSION) pyvenv pyvenv-$(VERSION) ;\
do \
ln -fs "$(prefix)/bin/$${fn}" "$(DESTDIR)$(FRAMEWORKUNIXTOOLSPREFIX)/bin/$${fn}" ;\
done
@@ -93,7 +93,7 @@ altinstallunixtools:
$(INSTALL) -d -m $(DIRMODE) "$(DESTDIR)$(FRAMEWORKUNIXTOOLSPREFIX)/bin" ;\
fi
for fn in python$(VERSION) pythonw$(VERSION) idle$(VERSION) \
- pydoc$(VERSION) python$(VERSION)-config 2to3-$(VERSION);\
+ pydoc$(VERSION) python$(VERSION)-config 2to3-$(VERSION) pyvenv-$(VERSION) ;\
do \
ln -fs "$(prefix)/bin/$${fn}" "$(DESTDIR)$(FRAMEWORKUNIXTOOLSPREFIX)/bin/$${fn}" ;\
done
diff --git a/Mac/Tools/pythonw.c b/Mac/Tools/pythonw.c
index 30c82ac476..ebee5312f8 100644
--- a/Mac/Tools/pythonw.c
+++ b/Mac/Tools/pythonw.c
@@ -150,6 +150,18 @@ setup_spawnattr(posix_spawnattr_t* spawnattr)
int
main(int argc, char **argv) {
char* exec_path = get_python_path();
+ static char path[PATH_MAX * 2];
+ static char real_path[PATH_MAX * 2];
+ int status;
+ uint32_t size = PATH_MAX * 2;
+
+ /* Set the original executable path in the environment. */
+ status = _NSGetExecutablePath(path, &size);
+ if (status == 0) {
+ if (realpath(path, real_path) != NULL) {
+ setenv("__PYTHONV_LAUNCHER__", real_path, 1);
+ }
+ }
/*
* Let argv[0] refer to the new interpreter. This is needed to
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 38ffa34ff3..7b4b2ff633 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -947,6 +947,8 @@ bininstall: altbininstall
(cd $(DESTDIR)$(BINDIR); $(LN) -s 2to3-$(VERSION) 2to3)
-rm -f $(DESTDIR)$(BINDIR)/pysetup3
(cd $(DESTDIR)$(BINDIR); $(LN) -s pysetup$(VERSION) pysetup3)
+ -rm -f $(DESTDIR)$(BINDIR)/pyvenv
+ (cd $(DESTDIR)$(BINDIR); $(LN) -s pyvenv-$(VERSION) pyvenv)
# Install the manual page
maninstall:
@@ -1038,6 +1040,7 @@ LIBSUBDIRS= tkinter tkinter/test tkinter/test/test_tkinter \
turtledemo \
multiprocessing multiprocessing/dummy \
unittest unittest/test unittest/test/testmock \
+ venv venv/scripts venv/scripts/posix \
curses pydoc_data $(MACHDEPS)
libinstall: build_all $(srcdir)/Lib/$(PLATDIR) $(srcdir)/Modules/xxmodule.c
@for i in $(SCRIPTDIR) $(LIBDEST); \
diff --git a/Modules/getpath.c b/Modules/getpath.c
index 709087994d..b15319723e 100644
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -260,6 +260,59 @@ absolutize(wchar_t *path)
wcscpy(path, buffer);
}
+/* search for a prefix value in an environment file. If found, copy it
+ to the provided buffer, which is expected to be no more than MAXPATHLEN
+ bytes long.
+*/
+
+static int
+find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value)
+{
+ int result = 0; /* meaning not found */
+ char buffer[MAXPATHLEN*2+1]; /* allow extra for key, '=', etc. */
+
+ fseek(env_file, 0, SEEK_SET);
+ while (!feof(env_file)) {
+ char * p = fgets(buffer, MAXPATHLEN*2, env_file);
+ wchar_t tmpbuffer[MAXPATHLEN*2+1];
+ PyObject * decoded;
+ int n;
+
+ if (p == NULL)
+ break;
+ n = strlen(p);
+ if (p[n - 1] != '\n') {
+ /* line has overflowed - bail */
+ break;
+ }
+ if (p[0] == '#') /* Comment - skip */
+ continue;
+ decoded = PyUnicode_DecodeUTF8(buffer, n, "surrogateescape");
+ if (decoded != NULL) {
+ Py_ssize_t k;
+ wchar_t * state;
+ k = PyUnicode_AsWideChar(decoded,
+ tmpbuffer, MAXPATHLEN * 2);
+ Py_DECREF(decoded);
+ if (k >= 0) {
+ wchar_t * tok = wcstok(tmpbuffer, L" \t\r\n", &state);
+ if ((tok != NULL) && !wcscmp(tok, key)) {
+ tok = wcstok(NULL, L" \t", &state);
+ if ((tok != NULL) && !wcscmp(tok, L"=")) {
+ tok = wcstok(NULL, L"\r\n", &state);
+ if (tok != NULL) {
+ wcsncpy(value, tok, MAXPATHLEN);
+ result = 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
/* search_for_prefix requires that argv0_path be no more than MAXPATHLEN
bytes long.
*/
@@ -565,6 +618,39 @@ calculate_path(void)
MAXPATHLEN bytes long.
*/
+ /* Search for an environment configuration file, first in the
+ executable's directory and then in the parent directory.
+ If found, open it for use when searching for prefixes.
+ */
+
+ {
+ wchar_t tmpbuffer[MAXPATHLEN+1];
+ wchar_t *env_cfg = L"pyvenv.cfg";
+ FILE * env_file = NULL;
+
+ wcscpy(tmpbuffer, argv0_path);
+ joinpath(tmpbuffer, env_cfg);
+ env_file = _Py_wfopen(tmpbuffer, L"r");
+ if (env_file == NULL) {
+ errno = 0;
+ reduce(tmpbuffer);
+ reduce(tmpbuffer);
+ joinpath(tmpbuffer, env_cfg);
+ env_file = _Py_wfopen(tmpbuffer, L"r");
+ if (env_file == NULL) {
+ errno = 0;
+ }
+ }
+ if (env_file != NULL) {
+ /* Look for a 'home' variable and set argv0_path to it, if found */
+ if (find_env_config_value(env_file, L"home", tmpbuffer)) {
+ wcscpy(argv0_path, tmpbuffer);
+ }
+ fclose(env_file);
+ env_file = NULL;
+ }
+ }
+
if (!(pfound = search_for_prefix(argv0_path, home, _prefix))) {
if (!Py_FrozenFlag)
fprintf(stderr,
diff --git a/PC/getpathp.c b/PC/getpathp.c
index 8921aa01fa..b5bf325f46 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -423,6 +423,53 @@ get_progpath(void)
progpath[0] = '\0';
}
+static int
+find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value)
+{
+ int result = 0; /* meaning not found */
+ char buffer[MAXPATHLEN*2+1]; /* allow extra for key, '=', etc. */
+
+ fseek(env_file, 0, SEEK_SET);
+ while (!feof(env_file)) {
+ char * p = fgets(buffer, MAXPATHLEN*2, env_file);
+ wchar_t tmpbuffer[MAXPATHLEN*2+1];
+ PyObject * decoded;
+ int n;
+
+ if (p == NULL)
+ break;
+ n = strlen(p);
+ if (p[n - 1] != '\n') {
+ /* line has overflowed - bail */
+ break;
+ }
+ if (p[0] == '#') /* Comment - skip */
+ continue;
+ decoded = PyUnicode_DecodeUTF8(buffer, n, "surrogateescape");
+ if (decoded != NULL) {
+ Py_ssize_t k;
+ k = PyUnicode_AsWideChar(decoded,
+ tmpbuffer, MAXPATHLEN * 2);
+ Py_DECREF(decoded);
+ if (k >= 0) {
+ wchar_t * tok = wcstok(tmpbuffer, L" \t\r\n");
+ if ((tok != NULL) && !wcscmp(tok, key)) {
+ tok = wcstok(NULL, L" \t");
+ if ((tok != NULL) && !wcscmp(tok, L"=")) {
+ tok = wcstok(NULL, L"\r\n");
+ if (tok != NULL) {
+ wcsncpy(value, tok, MAXPATHLEN);
+ result = 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
static void
calculate_path(void)
{
@@ -457,6 +504,40 @@ calculate_path(void)
/* progpath guaranteed \0 terminated in MAXPATH+1 bytes. */
wcscpy(argv0_path, progpath);
reduce(argv0_path);
+
+ /* Search for an environment configuration file, first in the
+ executable's directory and then in the parent directory.
+ If found, open it for use when searching for prefixes.
+ */
+
+ {
+ wchar_t tmpbuffer[MAXPATHLEN+1];
+ wchar_t *env_cfg = L"pyvenv.cfg";
+ FILE * env_file = NULL;
+
+ wcscpy(tmpbuffer, argv0_path);
+ join(tmpbuffer, env_cfg);
+ env_file = _Py_wfopen(tmpbuffer, L"r");
+ if (env_file == NULL) {
+ errno = 0;
+ reduce(tmpbuffer);
+ reduce(tmpbuffer);
+ join(tmpbuffer, env_cfg);
+ env_file = _Py_wfopen(tmpbuffer, L"r");
+ if (env_file == NULL) {
+ errno = 0;
+ }
+ }
+ if (env_file != NULL) {
+ /* Look for a 'home' variable and set argv0_path to it, if found */
+ if (find_env_config_value(env_file, L"home", tmpbuffer)) {
+ wcscpy(argv0_path, tmpbuffer);
+ }
+ fclose(env_file);
+ env_file = NULL;
+ }
+ }
+
if (pythonhome == NULL || *pythonhome == '\0') {
if (search_for_prefix(argv0_path, LANDMARK))
pythonhome = prefix;
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index c434b5a81e..57f880e9ec 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -1528,6 +1528,10 @@ _PySys_Init(void)
PyUnicode_FromWideChar(Py_GetPrefix(), -1));
SET_SYS_FROM_STRING("exec_prefix",
PyUnicode_FromWideChar(Py_GetExecPrefix(), -1));
+ SET_SYS_FROM_STRING("base_prefix",
+ PyUnicode_FromWideChar(Py_GetPrefix(), -1));
+ SET_SYS_FROM_STRING("base_exec_prefix",
+ PyUnicode_FromWideChar(Py_GetExecPrefix(), -1));
SET_SYS_FROM_STRING("maxsize",
PyLong_FromSsize_t(PY_SSIZE_T_MAX));
SET_SYS_FROM_STRING("float_info",
diff --git a/Tools/msi/msi.py b/Tools/msi/msi.py
index c29e6cad67..8e2f5a3f81 100644
--- a/Tools/msi/msi.py
+++ b/Tools/msi/msi.py
@@ -1122,6 +1122,7 @@ def add_files(db):
lib.add_file("2to3.py", src="2to3")
lib.add_file("pydoc3.py", src="pydoc3")
lib.add_file("pysetup3.py", src="pysetup3")
+ lib.add_file("pyvenv.py", src="pyvenv")
if have_tcl:
lib.start_component("pydocgui.pyw", tcltk, keyfile="pydocgui.pyw")
lib.add_file("pydocgui.pyw")
diff --git a/Tools/scripts/pyvenv b/Tools/scripts/pyvenv
new file mode 100755
index 0000000000..978d691d1e
--- /dev/null
+++ b/Tools/scripts/pyvenv
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+if __name__ == '__main__':
+ import sys
+ rc = 1
+ try:
+ import venv
+ venv.main()
+ rc = 0
+ except Exception as e:
+ print('Error: %s' % e, file=sys.stderr)
+ sys.exit(rc)
diff --git a/setup.py b/setup.py
index 155156b6dc..5bc5afe42f 100644
--- a/setup.py
+++ b/setup.py
@@ -431,7 +431,7 @@ class PyBuildExt(build_ext):
for directory in reversed(options.dirs):
add_dir_to_list(dir_list, directory)
- if os.path.normpath(sys.prefix) != '/usr' \
+ if os.path.normpath(sys.base_prefix) != '/usr' \
and not sysconfig.get_config_var('PYTHONFRAMEWORK'):
# OSX note: Don't add LIBDIR and INCLUDEDIR to building a framework
# (PYTHONFRAMEWORK is set) to avoid # linking problems when
@@ -1978,7 +1978,7 @@ class PyBuildScripts(build_scripts):
newoutfiles = []
newupdated_files = []
for filename in outfiles:
- if filename.endswith('2to3'):
+ if filename.endswith(('2to3', 'pyvenv')):
newfilename = filename + fullversion
else:
newfilename = filename + minoronly
@@ -2046,7 +2046,8 @@ def main():
# check the PyBuildScripts command above, and change the links
# created by the bininstall target in Makefile.pre.in
scripts = ["Tools/scripts/pydoc3", "Tools/scripts/idle3",
- "Tools/scripts/2to3", "Tools/scripts/pysetup3"]
+ "Tools/scripts/2to3", "Tools/scripts/pysetup3",
+ "Tools/scripts/pyvenv"]
)
# --install-platlib