summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Schulze <florian.schulze@gmx.net>2015-01-29 14:25:16 +0100
committerFlorian Schulze <florian.schulze@gmx.net>2015-01-29 14:25:16 +0100
commite98121c142011d51695181279be24ea9e8afc0d3 (patch)
treef874d62f00a9d81a8215137f61cef56f394cd642
parent1c6153be18ae650fd5b869f8fe5502ccc28c8ece (diff)
parentac65fa83ff498ab5ee02a3cc73deef3c30619a00 (diff)
downloadtox-e98121c142011d51695181279be24ea9e8afc0d3.tar.gz
Merged default
-rw-r--r--.hgignore1
-rw-r--r--[-rwxr-xr-x]CHANGELOG132
-rw-r--r--CONTRIBUTORS18
-rw-r--r--README.rst2
-rw-r--r--doc/announce/release-1.8.txt54
-rw-r--r--doc/conf.py3
-rw-r--r--doc/config-v2.txt2
-rw-r--r--doc/config.txt244
-rw-r--r--doc/example/basic.txt44
-rw-r--r--doc/example/devenv.txt81
-rw-r--r--doc/example/jenkins.txt7
-rwxr-xr-xdoc/example/pytest.txt21
-rw-r--r--doc/index.txt7
-rw-r--r--doc/install.txt2
-rw-r--r--setup.py9
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/test_config.py507
-rw-r--r--tests/test_interpreters.py2
-rw-r--r--tests/test_quickstart.py180
-rw-r--r--tests/test_result.py12
-rw-r--r--tests/test_venv.py88
-rw-r--r--tests/test_z_cmdline.py35
-rw-r--r--tox.ini8
-rw-r--r--tox/__init__.py4
-rw-r--r--tox/__main__.py3
-rw-r--r--tox/_cmdline.py39
-rw-r--r--tox/_config.py400
-rw-r--r--tox/_pytestplugin.py22
-rw-r--r--tox/_quickstart.py4
-rw-r--r--tox/_venv.py90
-rw-r--r--tox/_verlib.py1
-rw-r--r--tox/interpreters.py6
-rw-r--r--tox/result.py11
-rwxr-xr-xtox/vendor/virtualenv.py2581
34 files changed, 1610 insertions, 3012 deletions
diff --git a/.hgignore b/.hgignore
index cf24d86..8cacf58 100644
--- a/.hgignore
+++ b/.hgignore
@@ -14,3 +14,4 @@ dist
doc/_build/
tox.egg-info
.tox
+.cache
diff --git a/CHANGELOG b/CHANGELOG
index 8e4b71c..55ab8ae 100755..100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,139 @@
-1.6.2.dev
+1.9.0.dev
+-----------
+
+- fix issue193: Remove ``--pre`` from the default ``install_command``; by
+ default tox will now only install final releases from PyPI for unpinned
+ dependencies. Use ``pip_pre = true`` in a testenv or the ``--pre``
+ command-line option to restore the previous behavior.
+
+- fix issue199: fill resultlog structure ahead of virtualenv creation
+
+- refine determination if we run from Jenkins, thanks Borge Lanes.
+
+
+1.8.1
+-----------
+
+- fix issue190: allow setenv to be empty.
+
+- allow escaping curly braces with "\". Thanks Marc Abramowitz for the PR.
+
+- allow "." names in environment names such that "py27-django1.7" is a
+ valid environment name. Thanks Alex Gaynor and Alex Schepanovski.
+
+- report subprocess exit code when execution fails. Thanks Marius
+ Gedminas.
+
+1.8.0
+-----------
+
+- new multi-dimensional configuration support. Many thanks to
+ Alexander Schepanovski for the complete PR with docs.
+ And to Mike Bayer and others for testing and feedback.
+
+- fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting
+ subprocesses. Thanks Steven Myint.
+
+- fix issue152: set VIRTUAL_ENV when running test commands,
+ thanks Florian Ludwig.
+
+- better report if we can't get version_info from an interpreter
+ executable. Thanks Floris Bruynooghe.
+
+
+1.7.2
+-----------
+
+- fix issue150: parse {posargs} more like we used to do it pre 1.7.0.
+ The 1.7.0 behaviour broke a lot of OpenStack projects.
+ See PR85 and the issue discussions for (far) more details, hopefully
+ resulting in a more refined behaviour in the 1.8 series.
+ And thanks to Clark Boylan for the PR.
+
+- fix issue59: add a config variable ``skip-missing-interpreters`` as well as
+ command line option ``--skip-missing-interpreters`` which won't fail the
+ build if Python interpreters listed in tox.ini are missing. Thanks
+ Alexandre Conrad for PR104.
+
+- fix issue164: better traceback info in case of failing test commands.
+ Thanks Marc Abramowitz for PR92.
+
+- support optional env variable substitution, thanks Morgan Fainberg
+ for PR86.
+
+- limit python hashseed to 1024 on Windows to prevent possible
+ memory errors. Thanks March Schlaich for the PR90.
+
+1.7.1
+---------
+
+- fix issue162: don't list python 2.5 as compatibiliy/supported
+
+- fix issue158 and fix issue155: windows/virtualenv properly works now:
+ call virtualenv through "python -m virtualenv" with the same
+ interpreter which invoked tox. Thanks Chris Withers, Ionel Maries Cristian.
+
+1.7.0
---------
+- don't lookup "pip-script" anymore but rather just "pip" on windows
+ as this is a pip implementation detail and changed with pip-1.5.
+ It might mean that tox-1.7 is not able to install a different pip
+ version into a virtualenv anymore.
+
+- drop Python2.5 compatibility because it became too hard due
+ to the setuptools-2.0 dropping support. tox now has no
+ support for creating python2.5 based environments anymore
+ and all internal special-handling has been removed.
+
+- merged PR81: new option --force-dep which allows to
+ override tox.ini specified dependencies in setuptools-style.
+ For example "--force-dep 'django<1.6'" will make sure
+ that any environment using "django" as a dependency will
+ get the latest 1.5 release. Thanks Bruno Oliveria for
+ the complete PR.
+
+- merged PR125: tox now sets "PYTHONHASHSEED" to a random value
+ and offers a "--hashseed" option to repeat a test run with a specific seed.
+ You can also use --hashsheed=noset to instruct tox to leave the value
+ alone. Thanks Chris Jerdonek for all the work behind this.
+
+- fix issue132: removing zip_safe setting (so it defaults to false)
+ to allow installation of tox
+ via easy_install/eggs. Thanks Jenisys.
+
+- fix issue126: depend on virtualenv>=1.11.2 so that we can rely
+ (hopefully) on a pip version which supports --pre. (tox by default
+ uses to --pre). also merged in PR84 so that we now call "virtualenv"
+ directly instead of looking up interpreters. Thanks Ionel Maries Cristian.
+ This also fixes issue140.
+
+- fix issue130: you can now set install_command=easy_install {opts} {packages}
+ and expect it to work for repeated tox runs (previously it only worked
+ when always recreating). Thanks jenisys for precise reporting.
+
+- fix issue129: tox now uses Popen(..., universal_newlines=True) to force
+ creation of unicode stdout/stderr streams. fixes a problem on specific
+ platform configs when creating virtualenvs with Python3.3. Thanks
+ Jorgen Schäfer or investigation and solution sketch.
+
- fix issue128: enable full substitution in install_command,
thanks for the PR to Ronald Evers
+- rework and simplify "commands" parsing and in particular posargs
+ substitutions to avoid various win32/posix related quoting issues.
+
+- make sure that the --installpkg option trumps any usedevelop settings
+ in tox.ini or
+
+- introduce --no-network to tox's own test suite to skip tests
+ requiring networks
+
+- introduce --sitepackages to force sitepackages=True in all
+ environments.
+
+- fix issue105 -- don't depend on an existing HOME directory from tox tests.
+
1.6.1
-----
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index c65d497..ef8576d 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -3,6 +3,7 @@ contributions:
Krisztian Fekete
Marc Abramowitz
+Aleaxner Schepanovski
Sridhar Ratnakumar
Barry Warsaw
Chris Rose
@@ -11,3 +12,20 @@ Ronny Pfannschmidt
Lukasz Balcerzak
Philip Thiem
Monty Taylor
+Bruno Oliveira
+Ionel Maries Cristian
+Anatoly techntonik
+Matt Jeffery
+Chris Jerdonek
+Ronald Evers
+Carl Meyer
+Anthon van der Neuth
+Matt Good
+Mattieu Agopian
+Asmund Grammeltwedt
+Ionel Maries Cristian
+Alexandre Conrad
+Morgan Fainberg
+Marc Schlaich
+Clark Boylan
+Eugene Yunak
diff --git a/README.rst b/README.rst
index 0fb6c68..c107df0 100644
--- a/README.rst
+++ b/README.rst
@@ -21,5 +21,5 @@ For more information and the repository please checkout:
have fun,
-holger krekel, May 2013
+holger krekel, 2014
diff --git a/doc/announce/release-1.8.txt b/doc/announce/release-1.8.txt
new file mode 100644
index 0000000..b8a2218
--- /dev/null
+++ b/doc/announce/release-1.8.txt
@@ -0,0 +1,54 @@
+tox 1.8: Generative/combinatorial environments specs
+=============================================================================
+
+I am happy to announce tox 1.8 which implements parametrized environments.
+
+See https://tox.testrun.org/latest/config.html#generating-environments-conditional-settings
+for examples and the new backward compatible syntax in your tox.ini file.
+
+Many thanks to Alexander Schepanovski for implementing and refining
+it based on the specifcation draft.
+
+More documentation about tox in general:
+
+ http://tox.testrun.org/
+
+Installation:
+
+ pip install -U tox
+
+code hosting and issue tracking on bitbucket:
+
+ https://bitbucket.org/hpk42/tox
+
+What is tox?
+----------------
+
+tox standardizes and automates tedious test activities driven from a
+simple ``tox.ini`` file, including:
+
+* creation and management of different virtualenv environments
+ with different Python interpreters
+* packaging and installing your package into each of them
+* running your test tool of choice, be it nose, py.test or unittest2 or other tools such as "sphinx" doc checks
+* testing dev packages against each other without needing to upload to PyPI
+
+best,
+Holger Krekel, merlinux GmbH
+
+
+Changes 1.8 (compared to 1.7.2)
+---------------------------------------
+
+- new multi-dimensional configuration support. Many thanks to
+ Alexander Schepanovski for the complete PR with docs.
+ And to Mike Bayer and others for testing and feedback.
+
+- fix issue148: remove "__PYVENV_LAUNCHER__" from os.environ when starting
+ subprocesses. Thanks Steven Myint.
+
+- fix issue152: set VIRTUAL_ENV when running test commands,
+ thanks Florian Ludwig.
+
+- better report if we can't get version_info from an interpreter
+ executable. Thanks Floris Bruynooghe.
diff --git a/doc/conf.py b/doc/conf.py
index d2f74ed..3096b10 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -48,7 +48,8 @@ copyright = u'2013, holger krekel and others'
# built documents.
#
# The short X.Y version.
-release = version = "1.6.1"
+release = "1.8"
+version = "1.8.1"
# The full version, including alpha/beta/rc tags.
# The language for content autogenerated by Sphinx. Refer to documentation
diff --git a/doc/config-v2.txt b/doc/config-v2.txt
index cf4cf3b..42973c4 100644
--- a/doc/config-v2.txt
+++ b/doc/config-v2.txt
@@ -195,7 +195,7 @@ Default settings related to environments names/variants
tox comes with predefined settings for certain variants, namely:
* ``{easy,pip}`` use easy_install or pip respectively
-* ``{py24,py25,py26,py27,py31,py32,py33,pypy19]`` use the respective
+* ``{py24,py25,py26,py27,py31,py32,py33,py34,pypy19]`` use the respective
pythonNN or PyPy interpreter
* ``{win32,linux,darwin}`` defines the according ``platform``.
diff --git a/doc/config.txt b/doc/config.txt
index 8eb8f3a..6ffd02b 100644
--- a/doc/config.txt
+++ b/doc/config.txt
@@ -28,20 +28,31 @@ List of optional global options::
(by checking for existence of the ``JENKINS_URL`` environment variable)
and will first lookup global tox settings in this section::
- [tox:hudson]
- ... # override [tox] settings for the hudson context
- # note: for hudson distshare defaults to ``{toxworkdir}/distshare``.
+ [tox:jenkins]
+ ... # override [tox] settings for the jenkins context
+ # note: for jenkins distshare defaults to ``{toxworkdir}/distshare``.
+.. confval:: skip_missing_interpreters=BOOL
-envlist setting
-+++++++++++++++
+ .. versionadded:: 1.7.2
-Determining the environment list that ``tox`` is to operate on
-happens in this order:
+ Setting this to ``True`` is equivalent of passing the
+ ``--skip-missing-interpreters`` command line option, and will force ``tox`` to
+ return success even if some of the specified environments were missing. This is
+ useful for some CI systems or running on a developer box, where you might only
+ have a subset of all your supported interpreters installed but don't want to
+ mark the build as failed because of it. As expected, the command line switch
+ always overrides this setting if passed on the invokation.
+ **Default:** ``False``
-* command line option ``-eENVLIST``
-* environment variable ``TOXENV``
-* ``tox.ini`` file's ``envlist``
+.. confval:: envlist=CSV
+
+ Determining the environment list that ``tox`` is to operate on
+ happens in this order (if any is found, no further lookups are made):
+
+ * command line option ``-eENVLIST``
+ * environment variable ``TOXENV``
+ * ``tox.ini`` file's ``envlist``
Virtualenv test environment settings
@@ -80,7 +91,7 @@ Complete list of settings that you can put into ``testenv*`` sections:
.. versionadded:: 1.6
- **WARNING**: This setting is **EXPERIMENTAL** so use with care
+ **WARNING**: This setting is **EXPERIMENTAL** so use with care
and be ready to adapt your tox.ini's with post-1.6 tox releases.
the ``install_command`` setting is used for installing packages into
@@ -94,22 +105,29 @@ Complete list of settings that you can put into ``testenv*`` sections:
if you have configured it.
**default**::
-
- pip install --pre {opts} {packages}
-
- **default on environments using python2.5**::
- pip install {opts} {packages}``
+ pip install {opts} {packages}
+
+.. confval:: pip_pre=True|False(default)
+
+ .. versionadded:: 1.9
+
+ If ``True``, adds ``--pre`` to the ``opts`` passed to
+ :confval:`install_command`. If :confval:`install_command` uses pip, this
+ will cause it to install the latest available pre-release of any
+ dependencies without a specified version. If ``False`` (the default), pip
+ will only install final releases of unpinned dependencies.
- this will use pip<1.4 which has no ``--pre`` option. Note also
- that for python2.5 support you may need to install ssl and/or
- use ``setenv = PIP_INSECURE=1`` in a py25 based testenv.
+ Passing the ``--pre`` command-line option to tox will force this to
+ ``True`` for all testenvs.
+
+ Don't set this option if your :confval:`install_command` does not use pip.
.. confval:: whitelist_externals=MULTI-LINE-LIST
each line specifies a command name (in glob-style pattern format)
which can be used in the ``commands`` section without triggering
- a "not installed in virtualenv" warning. Example: if you use the
+ a "not installed in virtualenv" warning. Example: if you use the
unix ``make`` for running tests you can list ``whitelist_externals=make``
or ``whitelist_externals=/usr/bin/make`` if you want more precision.
If you don't want tox to issue a warning in any case, just use
@@ -136,7 +154,7 @@ Complete list of settings that you can put into ``testenv*`` sections:
using the ``{toxinidir}`` as the current working directory.
.. confval:: setenv=MULTI-LINE-LIST
-
+
.. versionadded:: 0.9
each line contains a NAME=VALUE environment variable setting which
@@ -164,17 +182,17 @@ Complete list of settings that you can put into ``testenv*`` sections:
**DEPRECATED** -- as of August 2013 you should use setuptools
which has merged most of distribute_ 's changes. Just use
- the default, Luke! In future versions of tox this option might
- be ignored and setuptools always chosen.
+ the default, Luke! In future versions of tox this option might
+ be ignored and setuptools always chosen.
**default:** False.
.. confval:: sitepackages=True|False
Set to ``True`` if you want to create virtual environments that also
- have access to globally installed packages.
+ have access to globally installed packages.
- **default:** False, meaning that virtualenvs will be
+ **default:** False, meaning that virtualenvs will be
created without inheriting the global site packages.
.. confval:: args_are_paths=BOOL
@@ -182,7 +200,7 @@ Complete list of settings that you can put into ``testenv*`` sections:
treat positional arguments passed to ``tox`` as file system paths
and - if they exist on the filesystem - rewrite them according
to the ``changedir``.
- **default**: True (due to the exists-on-filesystem check it's
+ **default**: True (due to the exists-on-filesystem check it's
usually safe to try rewriting).
.. confval:: envtmpdir=path
@@ -202,7 +220,7 @@ Complete list of settings that you can put into ``testenv*`` sections:
.. versionadded:: 0.9
Multi-line ``name = URL`` definitions of python package servers.
- Depedencies can specify using a specified index server through the
+ Dependencies can specify using a specified index server through the
``:indexservername:depname`` pattern. The ``default`` indexserver
definition determines where unscoped dependencies and the sdist install
installs from. Example::
@@ -241,6 +259,10 @@ Substitutions
Any ``key=value`` setting in an ini-file can make use
of value substitution through the ``{...}`` string-substitution pattern.
+You can escape curly braces with the ``\`` character if you need them, for example::
+
+ commands = echo "\{posargs\}" = {posargs}
+
Globally available substitutions
++++++++++++++++++++++++++++++++
@@ -292,6 +314,26 @@ then the value will be retrieved as ``os.environ['KEY']``
and raise an Error if the environment variable
does not exist.
+
+environment variable substitutions with default values
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+If you specify a substitution string like this::
+
+ {env:KEY:DEFAULTVALUE}
+
+then the value will be retrieved as ``os.environ['KEY']``
+and replace with DEFAULTVALUE if the environment variable does not
+exist.
+
+If you specify a substitution string like this::
+
+ {env:KEY:}
+
+then the value will be retrieved as ``os.environ['KEY']``
+and replace with and empty string if the environment variable does not
+exist.
+
.. _`command positional substitution`:
.. _`positional substitution`:
@@ -347,7 +389,7 @@ You can put default values in one section and reference them in others to avoid
pytest
mock
pytest-xdist
-
+
[testenv:dulwich]
deps =
dulwich
@@ -359,6 +401,152 @@ You can put default values in one section and reference them in others to avoid
{[base]deps}
+Generating environments, conditional settings
+---------------------------------------------
+
+.. versionadded:: 1.8
+
+Suppose you want to test your package against python2.6, python2.7 and against
+several versions of a dependency, say Django 1.5 and Django 1.6. You can
+accomplish that by writing down 2*2 = 4 ``[testenv:*]`` sections and then
+listing all of them in ``envlist``.
+
+However, a better approach looks like this::
+
+ [tox]
+ envlist = {py26,py27}-django{15,16}
+
+ [testenv]
+ basepython =
+ py26: python2.6
+ py27: python2.7
+ deps =
+ pytest
+ django15: Django>=1.5,<1.6
+ django16: Django>=1.6,<1.7
+ py26: unittest2
+ commands = py.test
+
+This uses two new facilities of tox-1.8:
+
+- generative envlist declarations where each envname
+ consists of environment parts or "factors"
+
+- "factor" specific settings
+
+Let's go through this step by step.
+
+
+Generative envlist
++++++++++++++++++++++++
+
+::
+
+ envlist = {py26,py27}-django{15,16}
+
+This is bash-style syntax and will create ``2*2=4`` environment names
+like this::
+
+ py26-django15
+ py26-django16
+ py27-django15
+ py27-django16
+
+You can still list environments explicitly along with generated ones::
+
+ envlist = {py26,py27}-django{15,16}, docs, flake
+
+.. note::
+
+ To help with understanding how the variants will produce section values,
+ you can ask tox to show their expansion with a new option::
+
+ $ tox -l
+ py26-django15
+ py26-django16
+ py27-django15
+ py27-django16
+ docs
+ flake
+
+
+Factors and factor-conditional settings
+++++++++++++++++++++++++++++++++++++++++
+
+Parts of an environment name delimited by hyphens are called factors and can
+be used to set values conditionally::
+
+ basepython =
+ py26: python2.6
+ py27: python2.7
+
+This conditional setting will lead to either ``python2.6`` or
+``python2.7`` used as base python, e.g. ``python2.6`` is selected if current
+environment contains ``py26`` factor.
+
+In list settings such as ``deps`` or ``commands`` you can freely intermix
+optional lines with unconditional ones::
+
+ deps =
+ pytest
+ django15: Django>=1.5,<1.6
+ django16: Django>=1.6,<1.7
+ py26: unittest2
+
+Reading it line by line:
+
+- ``pytest`` will be included unconditionally,
+- ``Django>=1.5,<1.6`` will be included for environments containing ``django15`` factor,
+- ``Django>=1.6,<1.7`` similarly depends on ``django16`` factor,
+- ``unittest`` will be loaded for Python 2.6 environments.
+
+.. note::
+
+ Tox provides good defaults for basepython setting, so the above
+ ini-file can be further reduced by omitting the ``basepython``
+ setting.
+
+
+Complex factor conditions
++++++++++++++++++++++++++
+
+Sometimes you need to specify same line for several factors or create a special
+case for a combination of factors. Here is how you do it::
+
+ [tox]
+ envlist = py{26,27,33}-django{15,16}-{sqlite,mysql}
+
+ [testenv]
+ deps =
+ py33-mysql: PyMySQL ; use if both py33 and mysql are in an env name
+ py26,py27: urllib3 ; use if any of py26 or py27 are in an env name
+ py{26,27}-sqlite: mock ; mocking sqlite in python 2.x
+
+Take a look at first ``deps`` line. It shows how you can special case something
+for a combination of factors, you just join combining factors with a hyphen.
+This particular line states that ``PyMySQL`` will be loaded for python 3.3,
+mysql environments, e.g. ``py33-django15-mysql`` and ``py33-django16-mysql``.
+
+The second line shows how you use same line for several factors - by listing
+them delimited by commas. It's possible to list not only simple factors, but
+also their combinations like ``py26-sqlite,py27-sqlite``.
+
+Finally, factor expressions are expanded the same way as envlist, so last
+example could be rewritten as ``py{26,27}-sqlite``.
+
+.. note::
+
+ Factors don't do substring matching against env name, instead every
+ hyphenated expression is split by ``-`` and if ALL the factors in an
+ expression are also factors of an env then that condition is considered
+ hold.
+
+ For example, environment ``py26-mysql``:
+
+ - could be matched with expressions ``py26``, ``py26-mysql``,
+ ``mysql-py26``,
+ - but not with ``py2`` or ``py26-sql``.
+
Other Rules and notes
=====================
diff --git a/doc/example/basic.txt b/doc/example/basic.txt
index 55b0848..49baeee 100644
--- a/doc/example/basic.txt
+++ b/doc/example/basic.txt
@@ -1,4 +1,3 @@
-
Basic usage
=============================================
@@ -13,6 +12,7 @@ reside next to your ``setup.py`` file::
[tox]
envlist = py26,py27
[testenv]
+ deps=pytest # or 'nose' or ...
commands=py.test # or 'nosetests' or ...
To sdist-package, install and test your project, you can
@@ -38,8 +38,10 @@ Available "default" test environments names are::
py31
py32
py33
+ py34
jython
pypy
+ pypy3
However, you can also create your own test environment names,
see some of the examples in :doc:`examples <../examples>`.
@@ -74,7 +76,7 @@ you can add it to your ``deps`` variable like this::
deps = -rrequirements.txt
-All installation commands are executed using ``{toxinidir}}``
+All installation commands are executed using ``{toxinidir}``
(the directory where ``tox.ini`` resides) as the current
working directory. Therefore, the underlying ``pip`` installation
will assume ``requirements.txt`` to exist at ``{toxinidir}/requirements.txt``.
@@ -175,6 +177,31 @@ a PYTHONPATH setting that will lead Python to also import
from the ``subdir`` below the directory where your ``tox.ini``
file resides.
+special handling of PYTHONHASHSEED
+-------------------------------------------
+
+.. versionadded:: 1.6.2
+
+By default, Tox sets PYTHONHASHSEED_ for test commands to a random integer
+generated when ``tox`` is invoked. This mimics Python's hash randomization
+enabled by default starting `in Python 3.3`_. To aid in reproducing test
+failures, Tox displays the value of ``PYTHONHASHSEED`` in the test output.
+
+You can tell Tox to use an explicit hash seed value via the ``--hashseed``
+command-line option to ``tox``. You can also override the hash seed value
+per test environment in ``tox.ini`` as follows::
+
+ [testenv]
+ setenv =
+ PYTHONHASHSEED = 100
+
+If you wish to disable this feature, you can pass the command line option
+``--hashseed=noset`` when ``tox`` is invoked. You can also disable it from the
+``tox.ini`` by setting ``PYTHONHASHSEED = 0`` as described above.
+
+.. _`in Python 3.3`: http://docs.python.org/3/whatsnew/3.3.html#builtin-functions-and-types
+.. _PYTHONHASHSEED: http://docs.python.org/using/cmdline.html#envvar-PYTHONHASHSEED
+
Integration with setuptools/distribute test commands
----------------------------------------------------
@@ -186,6 +213,10 @@ a test run when ``python setup.py test`` is issued::
import sys
class Tox(TestCommand):
+ user_options = [('tox-args=', 'a', "Arguments to pass to tox")]
+ def initialize_options(self):
+ TestCommand.initialize_options(self)
+ self.tox_args = None
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
@@ -193,7 +224,8 @@ a test run when ``python setup.py test`` is issued::
def run_tests(self):
#import here, cause outside the eggs aren't loaded
import tox
- errno = tox.cmdline(self.test_args)
+ import shlex
+ errno = tox.cmdline(args=shlex.split(self.tox_args))
sys.exit(errno)
setup(
@@ -206,5 +238,9 @@ Now if you run::
python setup.py test
-this will install tox and then run tox.
+this will install tox and then run tox. You can pass arguments to ``tox``
+using the ``--tox-args`` or ``-a`` command-line options. For example::
+
+ python setup.py test -a "-epy27"
+is equivalent to running ``tox -epy27``.
diff --git a/doc/example/devenv.txt b/doc/example/devenv.txt
index 571f89c..f4afc37 100644
--- a/doc/example/devenv.txt
+++ b/doc/example/devenv.txt
@@ -1,61 +1,78 @@
-
+=======================
Development environment
=======================
-Tox can be used to prepare development virtual environment for local projects.
-This feature can be useful in order to preserve environment across team members
-working on same project. It can be also used by deployment tools to prepare
-proper environments.
+Tox can be used for just preparing different virtual environments required by a
+project.
+
+This feature can be used by deployment tools when preparing deployed project
+environments. It can also be used for setting up normalized project development
+environments and thus help reduce the risk of different team members using
+mismatched development environments.
+
+Here are some examples illustrating how to set up a project's development
+environment using tox. For illustration purposes, let us call the development
+environment ``devenv``.
-Configuration
--------------
+Example 1: Basic scenario
+=========================
-Firstly, you need to prepare configuration for your development environment. In
-order to do that, we must define proper section at ``tox.ini`` file and tell at
-what directory environment should be created. Moreover, we need to specify
-python version that should be picked, and that the package should be installed
-with ``setup.py develop``::
+Step 1 - Configure the development environment
+----------------------------------------------
+
+First, we prepare the tox configuration for our development environment by
+defining a ``[testenv:devenv]`` section in the project's ``tox.ini``
+configuration file::
[testenv:devenv]
envdir = devenv
basepython = python2.7
usedevelop = True
- commands =
- deps =
+In it we state:
-Actually, you can configure a lot more, those are the only required settings.
-In example you can add ``deps`` and ``commands`` settings. Here, we tell tox
-not to pick ``commands`` or ``deps`` from base ``testenv`` configuration.
+- what directory to locate the environment in,
+- what Python executable to use in the environment,
+- that our project should be installed into the environment using ``setup.py
+ develop``, as opposed to building and installing its source distribution using
+ ``setup.py install``.
+Actually, we can configure a lot more, and these are only the required settings.
+For example, we can add the following to our configuration, telling tox not to
+reuse ``commands`` or ``deps`` settings from the base ``[testenv]``
+configuration::
+
+ commands =
+ deps =
-Creating development environment
---------------------------------
-Once ``devenv`` section is defined we can instrument tox to create our
-environment::
+Step 2 - Create the development environment
+-------------------------------------------
+
+Once the ``[testenv:devenv]`` configuration section has been defined, we create
+the actual development environment by running the following::
tox -e devenv
-This will create an environment at path specified by ``envdir`` under
-``[testenv:devenv]`` section.
+This creates the environment at the path specified by the environment's
+``envdir`` configuration value.
+
+Example 2: A more complex scenario
+==================================
-Full configuration example
---------------------------
+Let us say we want our project development environment to:
-Let's say we want our development environment sit at ``devenv`` and pull
-packages from ``requirements.txt`` file which we create at the same directory
-as ``tox.ini`` file. We also want to speciy Python version to be 2.7, and use
-``setup.py develop`` to work in development mode instead of building and
-installing an ``sdist`` package.
+- be located in the ``devenv`` directory,
+- use Python executable ``python2.7``,
+- pull packages from ``requirements.txt``, located in the same directory as
+ ``tox.ini``.
-Here is example configuration for that::
+Here is an example configuration for the described scenario::
[testenv:devenv]
envdir = devenv
basepython = python2.7
usedevelop = True
deps = -rrequirements.txt
-
diff --git a/doc/example/jenkins.txt b/doc/example/jenkins.txt
index bfdceee..2d8dc25 100644
--- a/doc/example/jenkins.txt
+++ b/doc/example/jenkins.txt
@@ -32,7 +32,9 @@ using these steps:
The last point requires that your test command creates JunitXML files,
for example with ``py.test`` it is done like this:
- commands=py.test --junitxml=junit-{envname}.xml
+.. code-block:: ini
+
+ commands = py.test --junitxml=junit-{envname}.xml
@@ -57,7 +59,7 @@ with this::
exec urllib.urlopen(url).read() in d
d['cmdline'](['--recreate'])
-The downloaded `toxbootstrap.py`_ file downloads all neccessary files to
+The downloaded `toxbootstrap.py` file downloads all neccessary files to
install ``tox`` in a virtual sub environment. Notes:
* uncomment the line containing ``USETOXDEV`` to use the latest
@@ -68,7 +70,6 @@ install ``tox`` in a virtual sub environment. Notes:
will cause tox to reinstall all virtual environments all the time
which is often what one wants in CI server contexts)
-.. _`toxbootstrap.py`: https://bitbucket.org/hpk42/tox/raw/default/toxbootstrap.py
Integrating "sphinx" documentation checks in a Jenkins job
----------------------------------------------------------------
diff --git a/doc/example/pytest.txt b/doc/example/pytest.txt
index 98fa077..6f98cf3 100755
--- a/doc/example/pytest.txt
+++ b/doc/example/pytest.txt
@@ -23,7 +23,7 @@ and the following ``tox.ini`` content::
deps=pytest # PYPI package providing py.test
commands=
py.test \
- [] # substitute with tox' positional arguments
+ {posargs} # substitute with tox' positional arguments
you can now invoke ``tox`` in the directory where your ``tox.ini`` resides.
``tox`` will sdist-package your project, create two virtualenv environments
@@ -49,7 +49,7 @@ and the following ``tox.ini`` content::
commands=
py.test \
--basetemp={envtmpdir} \ # py.test tempdir setting
- [] # substitute with tox' positional arguments
+ {posargs} # substitute with tox' positional arguments
you can invoke ``tox`` in the directory where your ``tox.ini`` resides.
Differently than in the previous example the ``py.test`` command
@@ -73,7 +73,7 @@ to make ``tox`` use this feature::
--basetemp={envtmpdir} \
--confcutdir=.. \
-n 3 \ # use three sub processes
- []
+ {posargs}
.. _`listed as a known issue`:
@@ -99,16 +99,13 @@ Alternatively, it is possible to use ``changedir`` so that checked-out
files are outside the import path, then pass ``--pyargs mypkg`` to
pytest.
-Installed tests are particularly convenient when combined with
-`Distribute's 2to3 support` (``use_2to3``).
+With tests that won't be installed, the simplest way to run them
+against your installed package is to avoid ``__init__.py`` files in test
+directories; pytest will still find and import them by adding their
+parent directory to ``sys.path`` but they won't be copied to
+other places or be found by Python's import system outside of pytest.
-With tests that won't be installed, the simplest way is to avoid
-``__init__.py`` files in test directories; pytest will still find them
-but they won't be copied to other places or be found by Python's import
-system.
+.. _`fully qualified name`: http://pytest.org/latest/goodpractises.html#test-package-name
-.. _`fully qualified name`: http://pytest.org/latest/goodpractises.html#package-name
-
-.. _`Distribute's 2to3 support`: http://packages.python.org/distribute/python3.html
.. include:: ../links.txt
diff --git a/doc/index.txt b/doc/index.txt
index 3e5aa99..17d063d 100644
--- a/doc/index.txt
+++ b/doc/index.txt
@@ -1,8 +1,6 @@
Welcome to the tox automation project
===============================================
-.. note:: second training: `professional testing with Python <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_ , 25-27th November 2013, Leipzig.
-
vision: standardize testing in Python
---------------------------------------------
@@ -70,9 +68,8 @@ Current features
support for configuring the installer command
through :confval:`install_command=ARGV`.
-* **cross-Python compatible**: Python-2.5 up to Python-3.3,
- Jython and pypy_ support. Python-2.5 is supported through
- a vendored ``virtualenv-1.9.1`` script.
+* **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher,
+ Jython and pypy_.
* **cross-platform**: Windows and Unix style environments
diff --git a/doc/install.txt b/doc/install.txt
index 5914ac1..2851571 100644
--- a/doc/install.txt
+++ b/doc/install.txt
@@ -4,7 +4,7 @@ tox installation
Install info in a nutshell
----------------------------------
-**Pythons**: CPython 2.4-3.3, Jython-2.5.1, pypy-1.9ff
+**Pythons**: CPython 2.6-3.3, Jython-2.5.1, pypy-1.9ff
**Operating systems**: Linux, Windows, OSX, Unix
diff --git a/setup.py b/setup.py
index a8eb184..46730a0 100644
--- a/setup.py
+++ b/setup.py
@@ -18,17 +18,15 @@ class Tox(TestCommand):
def main():
version = sys.version_info[:2]
- install_requires = ['virtualenv>=1.9.1', 'py>=1.4.15', ]
- if version < (2, 7) or (3, 0) <= version <= (3, 1):
+ install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ]
+ if version < (2, 7):
install_requires += ['argparse']
- if version < (2,6):
- install_requires += ["simplejson"]
setup(
name='tox',
description='virtualenv-based automation of test activities',
long_description=open("README.rst").read(),
url='http://tox.testrun.org/',
- version='1.6.2.dev1',
+ version='1.8.2.dev1',
license='http://opensource.org/licenses/MIT',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
author='holger krekel',
@@ -40,7 +38,6 @@ def main():
tests_require=['tox'],
cmdclass={"test": Tox},
install_requires=install_requires,
- zip_safe=True,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
diff --git a/tests/conftest.py b/tests/conftest.py
index 1d4bdde..3e2493b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,2 +1,2 @@
-from tox._pytestplugin import *
+from tox._pytestplugin import * # noqa
diff --git a/tests/test_config.py b/tests/test_config.py
index e117ca0..1c58a4a 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1,11 +1,11 @@
-import tox
-import pytest
-import os, sys
-import subprocess
+import sys
from textwrap import dedent
import py
-from tox._config import *
+import pytest
+import tox
+import tox._config
+from tox._config import * # noqa
from tox._config import _split_env
@@ -61,6 +61,43 @@ class TestVenvConfig:
envconfig = config.envconfigs['devenv']
assert envconfig.envdir == config.toxworkdir.join('foobar')
+ def test_force_dep_version(self, initproj):
+ """
+ Make sure we can override dependencies configured in tox.ini when using the command line option
+ --force-dep.
+ """
+ initproj("example123-0.5", filedefs={
+ 'tox.ini': '''
+ [tox]
+
+ [testenv]
+ deps=
+ dep1==1.0
+ dep2>=2.0
+ dep3
+ dep4==4.0
+ '''
+ })
+ config = parseconfig(
+ ['--force-dep=dep1==1.5', '--force-dep=dep2==2.1',
+ '--force-dep=dep3==3.0'])
+ assert config.option.force_dep== [
+ 'dep1==1.5', 'dep2==2.1', 'dep3==3.0']
+ assert [str(x) for x in config.envconfigs['python'].deps] == [
+ 'dep1==1.5', 'dep2==2.1', 'dep3==3.0', 'dep4==4.0',
+ ]
+
+ def test_is_same_dep(self):
+ """
+ Ensure correct parseini._is_same_dep is working with a few samples.
+ """
+ assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3')
+ assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>=2.0')
+ assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>2.0')
+ assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<2.0')
+ assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<=2.0')
+ assert not parseini._is_same_dep('pkg_hello-world3==1.0', 'otherpkg>=2.0')
+
class TestConfigPackage:
def test_defaults(self, tmpdir, newconfig):
config = newconfig([], "")
@@ -69,10 +106,10 @@ class TestConfigPackage:
envconfig = config.envconfigs['python']
assert envconfig.args_are_paths
assert not envconfig.recreate
+ assert not envconfig.pip_pre
def test_defaults_distshare(self, tmpdir, newconfig):
config = newconfig([], "")
- envconfig = config.envconfigs['python']
assert config.distshare == config.homedir.join(".tox", "distshare")
def test_defaults_changed_dir(self, tmpdir, newconfig):
@@ -111,6 +148,20 @@ def test_get_homedir(monkeypatch):
assert get_homedir() == "123"
+class TestGetcontextname:
+ def test_blank(self, monkeypatch):
+ monkeypatch.setattr(os, "environ", {})
+ assert getcontextname() is None
+
+ def test_jenkins(self, monkeypatch):
+ monkeypatch.setattr(os, "environ", {"JENKINS_URL": "xyz"})
+ assert getcontextname() == "jenkins"
+
+ def test_hudson_legacy(self, monkeypatch):
+ monkeypatch.setattr(os, "environ", {"HUDSON_URL": "xyz"})
+ assert getcontextname() == "jenkins"
+
+
class TestIniParser:
def test_getdefault_single(self, tmpdir, newconfig):
config = newconfig("""
@@ -130,6 +181,7 @@ class TestIniParser:
key2={xyz}
""")
reader = IniReader(config._cfg, fallbacksections=['mydefault'])
+ assert reader is not None
py.test.raises(tox.exception.ConfigError,
'reader.getdefault("mydefault", "key2")')
@@ -204,6 +256,22 @@ class TestIniParser:
py.test.raises(tox.exception.ConfigError,
'reader.getdefault("section", "key2")')
+ def test_getdefault_environment_substitution_with_default(self, monkeypatch, newconfig):
+ monkeypatch.setenv("KEY1", "hello")
+ config = newconfig("""
+ [section]
+ key1={env:KEY1:DEFAULT_VALUE}
+ key2={env:KEY2:DEFAULT_VALUE}
+ key3={env:KEY3:}
+ """)
+ reader = IniReader(config._cfg)
+ x = reader.getdefault("section", "key1")
+ assert x == "hello"
+ x = reader.getdefault("section", "key2")
+ assert x == "DEFAULT_VALUE"
+ x = reader.getdefault("section", "key3")
+ assert x == ""
+
def test_getdefault_other_section_substitution(self, newconfig):
config = newconfig("""
[section]
@@ -240,9 +308,19 @@ class TestIniParser:
# "reader.getargvlist('section', 'key1')")
assert reader.getargvlist('section', 'key1') == []
x = reader.getargvlist("section", "key2")
- assert x == [["cmd1", "with space", "grr"],
+ assert x == [["cmd1", "with", "space", "grr"],
["cmd2", "grr"]]
+ def test_argvlist_windows_escaping(self, tmpdir, newconfig):
+ config = newconfig("""
+ [section]
+ comm = py.test {posargs}
+ """)
+ reader = IniReader(config._cfg)
+ reader.addsubstitutions([r"hello\this"])
+ argv = reader.getargv("section", "comm")
+ assert argv == ["py.test", "hello\\this"]
+
def test_argvlist_multiline(self, tmpdir, newconfig):
config = newconfig("""
[section]
@@ -256,7 +334,7 @@ class TestIniParser:
# "reader.getargvlist('section', 'key1')")
assert reader.getargvlist('section', 'key1') == []
x = reader.getargvlist("section", "key2")
- assert x == [["cmd1", "with space", "grr"]]
+ assert x == [["cmd1", "with", "space", "grr"]]
def test_argvlist_quoting_in_command(self, tmpdir, newconfig):
@@ -298,7 +376,36 @@ class TestIniParser:
assert argvlist[0] == ["cmd1"]
assert argvlist[1] == ["cmd2", "value2", "other"]
- def test_positional_arguments_are_only_replaced_when_standing_alone(self, tmpdir, newconfig):
+ def test_argvlist_quoted_posargs(self, tmpdir, newconfig):
+ config = newconfig("""
+ [section]
+ key2=
+ cmd1 --foo-args='{posargs}'
+ cmd2 -f '{posargs}'
+ cmd3 -f {posargs}
+ """)
+ reader = IniReader(config._cfg)
+ reader.addsubstitutions(["foo", "bar"])
+ assert reader.getargvlist('section', 'key1') == []
+ x = reader.getargvlist("section", "key2")
+ assert x == [["cmd1", "--foo-args=foo bar"],
+ ["cmd2", "-f", "foo bar"],
+ ["cmd3", "-f", "foo", "bar"]]
+
+ def test_argvlist_posargs_with_quotes(self, tmpdir, newconfig):
+ config = newconfig("""
+ [section]
+ key2=
+ cmd1 -f {posargs}
+ """)
+ reader = IniReader(config._cfg)
+ reader.addsubstitutions(["foo", "'bar", "baz'"])
+ assert reader.getargvlist('section', 'key1') == []
+ x = reader.getargvlist("section", "key2")
+ assert x == [["cmd1", "-f", "foo", "bar baz"]]
+
+ def test_positional_arguments_are_only_replaced_when_standing_alone(self,
+ tmpdir, newconfig):
config = newconfig("""
[section]
key=
@@ -394,7 +501,25 @@ class TestConfigTestEnv:
assert envconfig.sitepackages == False
assert envconfig.develop == False
assert envconfig.envlogdir == envconfig.envdir.join("log")
- assert envconfig.setenv is None
+ assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED']
+ hashseed = envconfig.setenv['PYTHONHASHSEED']
+ assert isinstance(hashseed, str)
+ # The following line checks that hashseed parses to an integer.
+ int_hashseed = int(hashseed)
+ # hashseed is random by default, so we can't assert a specific value.
+ assert int_hashseed > 0
+
+ def test_sitepackages_switch(self, tmpdir, newconfig):
+ config = newconfig(["--sitepackages"], "")
+ envconfig = config.envconfigs['python']
+ assert envconfig.sitepackages == True
+
+ def test_installpkg_tops_develop(self, newconfig):
+ config = newconfig(["--installpkg=abc"], """
+ [testenv]
+ usedevelop = True
+ """)
+ assert not config.envconfigs["python"].develop
def test_specific_command_overrides(self, tmpdir, newconfig):
config = newconfig("""
@@ -444,7 +569,7 @@ class TestConfigTestEnv:
envconfig = config.envconfigs['python']
assert envconfig.envpython == envconfig.envbindir.join("python")
- @pytest.mark.parametrize("bp", ["jython", "pypy"])
+ @pytest.mark.parametrize("bp", ["jython", "pypy", "pypy3"])
def test_envbindir_jython(self, tmpdir, newconfig, bp):
config = newconfig("""
[testenv]
@@ -484,36 +609,6 @@ class TestConfigTestEnv:
assert envconfig.changedir.basename == "abc"
assert envconfig.changedir == config.setupdir.join("abc")
- def test_install_command_defaults_py25(self, newconfig, monkeypatch):
- from tox.interpreters import Interpreters
- def get_info(self, name):
- if "x25" in name:
- class I:
- runnable = True
- executable = "python2.5"
- version_info = (2,5)
- else:
- class I:
- runnable = False
- executable = "python"
- return I
- monkeypatch.setattr(Interpreters, "get_info", get_info)
- config = newconfig("""
- [testenv:x25]
- basepython = x25
- [testenv:py25-x]
- basepython = x25
- [testenv:py26]
- basepython = "python"
- """)
- for name in ("x25", "py25-x"):
- env = config.envconfigs[name]
- assert env.install_command == \
- "pip install {opts} {packages}".split()
- env = config.envconfigs["py26"]
- assert env.install_command == \
- "pip install --pre {opts} {packages}".split()
-
def test_install_command_setting(self, newconfig):
config = newconfig("""
[testenv]
@@ -540,6 +635,24 @@ class TestConfigTestEnv:
'some_install', '--arg=%s/foo' % config.toxinidir, 'python',
'{opts}', '{packages}']
+ def test_pip_pre(self, newconfig):
+ config = newconfig("""
+ [testenv]
+ pip_pre=true
+ """)
+ envconfig = config.envconfigs['python']
+ assert envconfig.pip_pre
+
+ def test_pip_pre_cmdline_override(self, newconfig):
+ config = newconfig(
+ ['--pre'],
+ """
+ [testenv]
+ pip_pre=false
+ """)
+ envconfig = config.envconfigs['python']
+ assert envconfig.pip_pre
+
def test_downloadcache(self, newconfig, monkeypatch):
monkeypatch.delenv("PIP_DOWNLOAD_CACHE", raising=False)
config = newconfig("""
@@ -567,14 +680,14 @@ class TestConfigTestEnv:
def test_simple(tmpdir, newconfig):
config = newconfig("""
- [testenv:py24]
- basepython=python2.4
- [testenv:py25]
- basepython=python2.5
+ [testenv:py26]
+ basepython=python2.6
+ [testenv:py27]
+ basepython=python2.7
""")
assert len(config.envconfigs) == 2
- assert "py24" in config.envconfigs
- assert "py25" in config.envconfigs
+ assert "py26" in config.envconfigs
+ assert "py27" in config.envconfigs
def test_substitution_error(tmpdir, newconfig):
py.test.raises(tox.exception.ConfigError, newconfig, """
@@ -626,6 +739,23 @@ class TestConfigTestEnv:
assert argv[0] == ["cmd1", "[hello]", "world"]
assert argv[1] == ["cmd1", "brave", "new", "world"]
+ def test_posargs_backslashed_or_quoted(self, tmpdir, newconfig):
+ inisource = """
+ [testenv:py24]
+ commands =
+ echo "\{posargs\}" = {posargs}
+ echo "posargs = " "{posargs}"
+ """
+ conf = newconfig([], inisource).envconfigs['py24']
+ argv = conf.commands
+ assert argv[0] == ['echo', '\\{posargs\\}', '=']
+ assert argv[1] == ['echo', 'posargs = ', ""]
+
+ conf = newconfig(['dog', 'cat'], inisource).envconfigs['py24']
+ argv = conf.commands
+ assert argv[0] == ['echo', '\\{posargs\\}', '=', 'dog', 'cat']
+ assert argv[1] == ['echo', 'posargs = ', 'dog cat']
+
def test_rewrite_posargs(self, tmpdir, newconfig):
inisource = """
[testenv:py24]
@@ -752,6 +882,100 @@ class TestConfigTestEnv:
assert conf.changedir.basename == 'testing'
assert conf.changedir.dirpath().realpath() == tmpdir.realpath()
+ def test_factors(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = a-x,b
+
+ [testenv]
+ deps=
+ dep-all
+ a: dep-a
+ b: dep-b
+ x: dep-x
+ """
+ conf = newconfig([], inisource)
+ configs = conf.envconfigs
+ assert [dep.name for dep in configs['a-x'].deps] == \
+ ["dep-all", "dep-a", "dep-x"]
+ assert [dep.name for dep in configs['b'].deps] == ["dep-all", "dep-b"]
+
+ def test_factor_ops(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = {a,b}-{x,y}
+
+ [testenv]
+ deps=
+ a,b: dep-a-or-b
+ a-x: dep-a-and-x
+ {a,b}-y: dep-ab-and-y
+ """
+ configs = newconfig([], inisource).envconfigs
+ get_deps = lambda env: [dep.name for dep in configs[env].deps]
+ assert get_deps("a-x") == ["dep-a-or-b", "dep-a-and-x"]
+ assert get_deps("a-y") == ["dep-a-or-b", "dep-ab-and-y"]
+ assert get_deps("b-x") == ["dep-a-or-b"]
+ assert get_deps("b-y") == ["dep-a-or-b", "dep-ab-and-y"]
+
+ def test_default_factors(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = py{26,27,33,34}-dep
+
+ [testenv]
+ deps=
+ dep: dep
+ """
+ conf = newconfig([], inisource)
+ configs = conf.envconfigs
+ for name, config in configs.items():
+ assert config.basepython == 'python%s.%s' % (name[2], name[3])
+
+ @pytest.mark.issue188
+ def test_factors_in_boolean(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = py{27,33}
+
+ [testenv]
+ recreate =
+ py27: True
+ """
+ configs = newconfig([], inisource).envconfigs
+ assert configs["py27"].recreate
+ assert not configs["py33"].recreate
+
+ @pytest.mark.issue190
+ def test_factors_in_setenv(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = py27,py26
+
+ [testenv]
+ setenv =
+ py27: X = 1
+ """
+ configs = newconfig([], inisource).envconfigs
+ assert configs["py27"].setenv["X"] == "1"
+ assert "X" not in configs["py26"].setenv
+
+ def test_period_in_factor(self, newconfig):
+ inisource="""
+ [tox]
+ envlist = py27-{django1.6,django1.7}
+
+ [testenv]
+ deps =
+ django1.6: Django==1.6
+ django1.7: Django==1.7
+ """
+ configs = newconfig([], inisource).envconfigs
+ assert sorted(configs) == ["py27-django1.6", "py27-django1.7"]
+ assert [d.name for d in configs["py27-django1.6"].deps] \
+ == ["Django==1.6"]
+
+
class TestGlobalOptions:
def test_notest(self, newconfig):
config = newconfig([], "")
@@ -836,7 +1060,7 @@ class TestGlobalOptions:
assert str(env.basepython) == sys.executable
def test_default_environments(self, tmpdir, newconfig, monkeypatch):
- envs = "py24,py25,py26,py27,py31,py32,jython,pypy"
+ envs = "py26,py27,py31,py32,py33,py34,jython,pypy,pypy3"
inisource = """
[tox]
envlist = %s
@@ -848,13 +1072,30 @@ class TestGlobalOptions:
env = config.envconfigs[name]
if name == "jython":
assert env.basepython == "jython"
- elif name == "pypy":
- assert env.basepython == "pypy"
+ elif name.startswith("pypy"):
+ assert env.basepython == name
else:
assert name.startswith("py")
bp = "python%s.%s" %(name[2], name[3])
assert env.basepython == bp
+ def test_envlist_expansion(self, newconfig):
+ inisource = """
+ [tox]
+ envlist = py{26,27},docs
+ """
+ config = newconfig([], inisource)
+ assert config.envlist == ["py26", "py27", "docs"]
+
+ def test_envlist_cross_product(self, newconfig):
+ inisource = """
+ [tox]
+ envlist = py{26,27}-dep{1,2}
+ """
+ config = newconfig([], inisource)
+ assert config.envlist == \
+ ["py26-dep1", "py26-dep2", "py27-dep1", "py27-dep2"]
+
def test_minversion(self, tmpdir, newconfig, monkeypatch):
inisource = """
[tox]
@@ -863,6 +1104,22 @@ class TestGlobalOptions:
config = newconfig([], inisource)
assert config.minversion == "3.0"
+ def test_skip_missing_interpreters_true(self, tmpdir, newconfig, monkeypatch):
+ inisource = """
+ [tox]
+ skip_missing_interpreters = True
+ """
+ config = newconfig([], inisource)
+ assert config.option.skip_missing_interpreters
+
+ def test_skip_missing_interpreters_false(self, tmpdir, newconfig, monkeypatch):
+ inisource = """
+ [tox]
+ skip_missing_interpreters = False
+ """
+ config = newconfig([], inisource)
+ assert not config.option.skip_missing_interpreters
+
def test_defaultenv_commandline(self, tmpdir, newconfig, monkeypatch):
config = newconfig(["-epy24"], "")
env = config.envconfigs['py24']
@@ -881,6 +1138,123 @@ class TestGlobalOptions:
assert env.basepython == "python2.4"
assert env.commands == [['xyz']]
+class TestHashseedOption:
+
+ def _get_envconfigs(self, newconfig, args=None, tox_ini=None,
+ make_hashseed=None):
+ if args is None:
+ args = []
+ if tox_ini is None:
+ tox_ini = """
+ [testenv]
+ """
+ if make_hashseed is None:
+ make_hashseed = lambda: '123456789'
+ original_make_hashseed = tox._config.make_hashseed
+ tox._config.make_hashseed = make_hashseed
+ try:
+ config = newconfig(args, tox_ini)
+ finally:
+ tox._config.make_hashseed = original_make_hashseed
+ return config.envconfigs
+
+ def _get_envconfig(self, newconfig, args=None, tox_ini=None):
+ envconfigs = self._get_envconfigs(newconfig, args=args,
+ tox_ini=tox_ini)
+ return envconfigs["python"]
+
+ def _check_hashseed(self, envconfig, expected):
+ assert envconfig.setenv == {'PYTHONHASHSEED': expected}
+
+ def _check_testenv(self, newconfig, expected, args=None, tox_ini=None):
+ envconfig = self._get_envconfig(newconfig, args=args, tox_ini=tox_ini)
+ self._check_hashseed(envconfig, expected)
+
+ def test_default(self, tmpdir, newconfig):
+ self._check_testenv(newconfig, '123456789')
+
+ def test_passing_integer(self, tmpdir, newconfig):
+ args = ['--hashseed', '1']
+ self._check_testenv(newconfig, '1', args=args)
+
+ def test_passing_string(self, tmpdir, newconfig):
+ args = ['--hashseed', 'random']
+ self._check_testenv(newconfig, 'random', args=args)
+
+ def test_passing_empty_string(self, tmpdir, newconfig):
+ args = ['--hashseed', '']
+ self._check_testenv(newconfig, '', args=args)
+
+ @pytest.mark.xfail(sys.version_info >= (3,2),
+ reason="at least Debian python 3.2/3.3 have a bug: "
+ "http://bugs.python.org/issue11884")
+ def test_passing_no_argument(self, tmpdir, newconfig):
+ """Test that passing no arguments to --hashseed is not allowed."""
+ args = ['--hashseed']
+ try:
+ self._check_testenv(newconfig, '', args=args)
+ except SystemExit:
+ e = sys.exc_info()[1]
+ assert e.code == 2
+ return
+ assert False # getting here means we failed the test.
+
+ def test_setenv(self, tmpdir, newconfig):
+ """Check that setenv takes precedence."""
+ tox_ini = """
+ [testenv]
+ setenv =
+ PYTHONHASHSEED = 2
+ """
+ self._check_testenv(newconfig, '2', tox_ini=tox_ini)
+ args = ['--hashseed', '1']
+ self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini)
+
+ def test_noset(self, tmpdir, newconfig):
+ args = ['--hashseed', 'noset']
+ envconfig = self._get_envconfig(newconfig, args=args)
+ assert envconfig.setenv is None
+
+ def test_noset_with_setenv(self, tmpdir, newconfig):
+ tox_ini = """
+ [testenv]
+ setenv =
+ PYTHONHASHSEED = 2
+ """
+ args = ['--hashseed', 'noset']
+ self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini)
+
+ def test_one_random_hashseed(self, tmpdir, newconfig):
+ """Check that different testenvs use the same random seed."""
+ tox_ini = """
+ [testenv:hash1]
+ [testenv:hash2]
+ """
+ next_seed = [1000]
+ # This function is guaranteed to generate a different value each time.
+ def make_hashseed():
+ next_seed[0] += 1
+ return str(next_seed[0])
+ # Check that make_hashseed() works.
+ assert make_hashseed() == '1001'
+ envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini,
+ make_hashseed=make_hashseed)
+ self._check_hashseed(envconfigs["hash1"], '1002')
+ # Check that hash2's value is not '1003', for example.
+ self._check_hashseed(envconfigs["hash2"], '1002')
+
+ def test_setenv_in_one_testenv(self, tmpdir, newconfig):
+ """Check using setenv in one of multiple testenvs."""
+ tox_ini = """
+ [testenv:hash1]
+ setenv =
+ PYTHONHASHSEED = 2
+ [testenv:hash2]
+ """
+ envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini)
+ self._check_hashseed(envconfigs["hash1"], '2')
+ self._check_hashseed(envconfigs["hash2"], '123456789')
+
class TestIndexServer:
def test_indexserver(self, tmpdir, newconfig):
config = newconfig("""
@@ -1000,6 +1374,29 @@ class TestCmdInvocation:
"*ERROR*tox.ini*not*found*",
])
+ def test_showconfig_with_force_dep_version(self, cmd, initproj):
+ initproj('force_dep_version', filedefs={
+ 'tox.ini': '''
+ [tox]
+
+ [testenv]
+ deps=
+ dep1==2.3
+ dep2
+ ''',
+ })
+ result = cmd.run("tox", "--showconfig")
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([
+ r'*deps=*dep1==2.3, dep2*',
+ ])
+ # override dep1 specific version, and force version for dep2
+ result = cmd.run("tox", "--showconfig", "--force-dep=dep1",
+ "--force-dep=dep2==5.0")
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([
+ r'*deps=*dep1, dep2==5.0*',
+ ])
class TestArgumentParser:
@@ -1077,3 +1474,13 @@ class TestCommandParser:
p = CommandParser(cmd)
parsed = list(p.words())
assert parsed == ['nosetests', ' ', '-v', ' ', '-a', ' ', '!deferred', ' ', '--with-doctest', ' ', '[]']
+
+
+ @pytest.mark.skipif("sys.platform != 'win32'")
+ def test_commands_with_backslash(self, newconfig):
+ config = newconfig([r"hello\world"], """
+ [testenv:py26]
+ commands = some {posargs}
+ """)
+ envconfig = config.envconfigs["py26"]
+ assert envconfig.commands[0] == ["some", r"hello\world"]
diff --git a/tests/test_interpreters.py b/tests/test_interpreters.py
index 39eabc6..f06a69a 100644
--- a/tests/test_interpreters.py
+++ b/tests/test_interpreters.py
@@ -2,7 +2,7 @@ import sys
import os
import pytest
-from tox.interpreters import *
+from tox.interpreters import * # noqa
@pytest.fixture
def interpreters():
diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py
index 772e905..df8a98f 100644
--- a/tests/test_quickstart.py
+++ b/tests/test_quickstart.py
@@ -6,6 +6,7 @@ import tox._quickstart
def cleandir(tmpdir):
tmpdir.chdir()
+
class TestToxQuickstartMain(object):
def mock_term_input_return_values(self, return_values):
@@ -23,10 +24,26 @@ class TestToxQuickstartMain(object):
return mock_term_input
- def test_quickstart_main_choose_individual_pythons_and_pytest(self, monkeypatch):
+ def test_quickstart_main_choose_individual_pythons_and_pytest(
+ self,
+ monkeypatch):
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'py.test', 'pytest']))
+ self.get_mock_term_input(
+ [
+ '4', # Python versions: choose one by one
+ 'Y', # py26
+ 'Y', # py27
+ 'Y', # py32
+ 'Y', # py33
+ 'Y', # py34
+ 'Y', # pypy
+ 'N', # jython
+ 'py.test', # command to run tests
+ 'pytest' # test dependencies
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
@@ -37,7 +54,7 @@ class TestToxQuickstartMain(object):
# and then run "tox" from this directory.
[tox]
-envlist = py24, py25, py26, py27, py32, py33, pypy
+envlist = py26, py27, py32, py33, py34, pypy
[testenv]
commands = py.test
@@ -47,10 +64,26 @@ deps =
result = open('tox.ini').read()
assert(result == expected_tox_ini)
- def test_quickstart_main_choose_individual_pythons_and_nose_adds_deps(self, monkeypatch):
+ def test_quickstart_main_choose_individual_pythons_and_nose_adds_deps(
+ self,
+ monkeypatch):
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'nosetests', '']))
+ self.get_mock_term_input(
+ [
+ '4', # Python versions: choose one by one
+ 'Y', # py26
+ 'Y', # py27
+ 'Y', # py32
+ 'Y', # py33
+ 'Y', # py34
+ 'Y', # pypy
+ 'N', # jython
+ 'nosetests', # command to run tests
+ '' # test dependencies
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
@@ -61,7 +94,7 @@ deps =
# and then run "tox" from this directory.
[tox]
-envlist = py24, py25, py26, py27, py32, py33, pypy
+envlist = py26, py27, py32, py33, py34, pypy
[testenv]
commands = nosetests
@@ -71,10 +104,26 @@ deps =
result = open('tox.ini').read()
assert(result == expected_tox_ini)
- def test_quickstart_main_choose_individual_pythons_and_trial_adds_deps(self, monkeypatch):
+ def test_quickstart_main_choose_individual_pythons_and_trial_adds_deps(
+ self,
+ monkeypatch):
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'trial', '']))
+ self.get_mock_term_input(
+ [
+ '4', # Python versions: choose one by one
+ 'Y', # py26
+ 'Y', # py27
+ 'Y', # py32
+ 'Y', # py33
+ 'Y', # py34
+ 'Y', # pypy
+ 'N', # jython
+ 'trial', # command to run tests
+ '' # test dependencies
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
@@ -85,7 +134,7 @@ deps =
# and then run "tox" from this directory.
[tox]
-envlist = py24, py25, py26, py27, py32, py33, pypy
+envlist = py26, py27, py32, py33, py34, pypy
[testenv]
commands = trial
@@ -95,11 +144,26 @@ deps =
result = open('tox.ini').read()
assert(result == expected_tox_ini)
- def test_quickstart_main_choose_individual_pythons_and_pytest_adds_deps(self, monkeypatch):
+ def test_quickstart_main_choose_individual_pythons_and_pytest_adds_deps(
+ self,
+ monkeypatch):
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['4', 'Y', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N', 'py.test', '']))
-
+ self.get_mock_term_input(
+ [
+ '4', # Python versions: choose one by one
+ 'Y', # py26
+ 'Y', # py27
+ 'Y', # py32
+ 'Y', # py33
+ 'Y', # py34
+ 'Y', # pypy
+ 'N', # jython
+ 'py.test', # command to run tests
+ '' # test dependencies
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
expected_tox_ini = """
@@ -109,7 +173,7 @@ deps =
# and then run "tox" from this directory.
[tox]
-envlist = py24, py25, py26, py27, py32, py33, pypy
+envlist = py26, py27, py32, py33, py34, pypy
[testenv]
commands = py.test
@@ -119,10 +183,19 @@ deps =
result = open('tox.ini').read()
assert(result == expected_tox_ini)
- def test_quickstart_main_choose_py27_and_pytest_adds_deps(self, monkeypatch):
+ def test_quickstart_main_choose_py27_and_pytest_adds_deps(
+ self,
+ monkeypatch):
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['1', 'py.test', '']))
+ self.get_mock_term_input(
+ [
+ '1', # py27
+ 'py.test', # command to run tests
+ '' # test dependencies
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
@@ -143,10 +216,19 @@ deps =
result = open('tox.ini').read()
assert(result == expected_tox_ini)
- def test_quickstart_main_choose_py27_and_py33_and_pytest_adds_deps(self, monkeypatch):
+ def test_quickstart_main_choose_py27_and_py33_and_pytest_adds_deps(
+ self,
+ monkeypatch):
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['2', 'py.test', '']))
+ self.get_mock_term_input(
+ [
+ '2', # py27 and py33
+ 'py.test', # command to run tests
+ '' # test dependencies
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
@@ -167,10 +249,19 @@ deps =
result = open('tox.ini').read()
assert(result == expected_tox_ini)
- def test_quickstart_main_choose_all_pythons_and_pytest_adds_deps(self, monkeypatch):
+ def test_quickstart_main_choose_all_pythons_and_pytest_adds_deps(
+ self,
+ monkeypatch):
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['3', 'py.test', '']))
+ self.get_mock_term_input(
+ [
+ '3', # all Python versions
+ 'py.test', # command to run tests
+ '' # test dependencies
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
@@ -181,7 +272,7 @@ deps =
# and then run "tox" from this directory.
[tox]
-envlist = py24, py25, py26, py27, py30, py31, py32, py33, pypy, jython
+envlist = py26, py27, py32, py33, py34, pypy, jython
[testenv]
commands = py.test
@@ -191,10 +282,26 @@ deps =
result = open('tox.ini').read()
assert(result == expected_tox_ini)
- def test_quickstart_main_choose_individual_pythons_and_defaults(self, monkeypatch):
+ def test_quickstart_main_choose_individual_pythons_and_defaults(
+ self,
+ monkeypatch):
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', '', '', '']))
+ self.get_mock_term_input(
+ [
+ '4', # Python versions: choose one by one
+ '', # py26
+ '', # py27
+ '', # py32
+ '', # py33
+ '', # py34
+ '', # pypy
+ '', # jython
+ '', # command to run tests
+ '' # test dependencies
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
@@ -205,7 +312,7 @@ deps =
# and then run "tox" from this directory.
[tox]
-envlist = py24, py25, py26, py27, py30, py31, py32, py33, pypy, jython
+envlist = py26, py27, py32, py33, py34, pypy, jython
[testenv]
commands = {envpython} setup.py test
@@ -224,7 +331,22 @@ deps =
monkeypatch.setattr(
tox._quickstart, 'term_input',
- self.get_mock_term_input(['4', '', '', '', '', '', '', '', '', '', '', '', '', '']))
+ self.get_mock_term_input(
+ [
+ '4', # Python versions: choose one by one
+ '', # py26
+ '', # py27
+ '', # py32
+ '', # py33
+ '', # py34
+ '', # pypy
+ '', # jython
+ '', # command to run tests
+ '', # test dependencies
+ '', # tox.ini already exists; overwrite?
+ ]
+ )
+ )
tox._quickstart.main(argv=['tox-quickstart'])
@@ -235,7 +357,7 @@ deps =
# and then run "tox" from this directory.
[tox]
-envlist = py24, py25, py26, py27, py30, py31, py32, py33, pypy, jython
+envlist = py26, py27, py32, py33, py34, pypy, jython
[testenv]
commands = {envpython} setup.py test
@@ -249,12 +371,11 @@ deps =
class TestToxQuickstart(object):
def test_pytest(self):
d = {
- 'py24': True,
- 'py25': True,
'py26': True,
'py27': True,
'py32': True,
'py33': True,
+ 'py34': True,
'pypy': True,
'commands': 'py.test',
'deps': 'pytest',
@@ -266,7 +387,7 @@ class TestToxQuickstart(object):
# and then run "tox" from this directory.
[tox]
-envlist = py24, py25, py26, py27, py32, py33, pypy
+envlist = py26, py27, py32, py33, py34, pypy
[testenv]
commands = py.test
@@ -337,6 +458,7 @@ deps =
'py27': True,
'py32': True,
'py33': True,
+ 'py34': True,
'pypy': True,
'commands': 'nosetests -v',
'deps': 'nose',
@@ -348,7 +470,7 @@ deps =
# and then run "tox" from this directory.
[tox]
-envlist = py27, py32, py33, pypy
+envlist = py27, py32, py33, py34, pypy
[testenv]
commands = nosetests -v
diff --git a/tests/test_result.py b/tests/test_result.py
index 6df834a..206a99c 100644
--- a/tests/test_result.py
+++ b/tests/test_result.py
@@ -10,6 +10,18 @@ def pkg(tmpdir):
p.write("whatever")
return p
+def test_pre_set_header(pkg):
+ replog = ResultLog()
+ d = replog.dict
+ assert replog.dict == d
+ assert replog.dict["reportversion"] == "1"
+ assert replog.dict["toxversion"] == tox.__version__
+ assert replog.dict["platform"] == sys.platform
+ assert replog.dict["host"] == py.std.socket.getfqdn()
+ data = replog.dumps_json()
+ replog2 = ResultLog.loads_json(data)
+ assert replog2.dict == replog.dict
+
def test_set_header(pkg):
replog = ResultLog()
d = replog.dict
diff --git a/tests/test_venv.py b/tests/test_venv.py
index 67bdb92..942888f 100644
--- a/tests/test_venv.py
+++ b/tests/test_venv.py
@@ -2,9 +2,9 @@ import py
import tox
import pytest
import os, sys
-from tox._venv import *
-
-py25calls = int(sys.version_info[:2] == (2,5))
+import tox._config
+from tox._venv import * # noqa
+from tox.interpreters import NoInterpreterInfo
#def test_global_virtualenv(capfd):
# v = VirtualEnv()
@@ -35,6 +35,12 @@ def test_getsupportedinterpreter(monkeypatch, newconfig, mocksession):
monkeypatch.setattr(venv.envconfig, 'basepython', 'notexistingpython')
py.test.raises(tox.exception.InterpreterNotFound,
venv.getsupportedinterpreter)
+ monkeypatch.undo()
+ # check that we properly report when no version_info is present
+ info = NoInterpreterInfo(name=venv.name)
+ info.executable = "something"
+ monkeypatch.setattr(config.interpreters, "get_info", lambda *args: info)
+ pytest.raises(tox.exception.InvocationError, venv.getsupportedinterpreter)
def test_create(monkeypatch, mocksession, newconfig):
@@ -118,6 +124,8 @@ def test_create_sitepackages(monkeypatch, mocksession, newconfig):
def test_install_deps_wildcard(newmocksession):
mocksession = newmocksession([], """
+ [tox]
+ distshare = {toxworkdir}/distshare
[testenv:py123]
deps=
{distshare}/dep1-*
@@ -125,13 +133,13 @@ def test_install_deps_wildcard(newmocksession):
venv = mocksession.getenv("py123")
venv.create()
l = mocksession._pcalls
- assert len(l) == 1 + py25calls
+ assert len(l) == 1
distshare = venv.session.config.distshare
distshare.ensure("dep1-1.0.zip")
distshare.ensure("dep1-1.1.zip")
venv.install_deps()
- assert len(l) == 2 + py25calls
+ assert len(l) == 2
args = l[-1].args
assert l[-1].cwd == venv.envconfig.config.toxinidir
assert "pip" in str(args[0])
@@ -158,10 +166,10 @@ def test_install_downloadcache(newmocksession, monkeypatch, tmpdir, envdc):
venv = mocksession.getenv("py123")
venv.create()
l = mocksession._pcalls
- assert len(l) == 1 + py25calls
+ assert len(l) == 1
venv.install_deps()
- assert len(l) == 2 + py25calls
+ assert len(l) == 2
args = l[-1].args
assert l[-1].cwd == venv.envconfig.config.toxinidir
assert "pip" in str(args[0])
@@ -186,7 +194,7 @@ def test_install_deps_indexserver(newmocksession):
venv = mocksession.getenv('py123')
venv.create()
l = mocksession._pcalls
- assert len(l) == 1 + py25calls
+ assert len(l) == 1
l[:] = []
venv.install_deps()
@@ -203,6 +211,25 @@ def test_install_deps_indexserver(newmocksession):
assert "-i ABC" in args
assert "dep3" in args
+def test_install_deps_pre(newmocksession):
+ mocksession = newmocksession([], """
+ [testenv]
+ pip_pre=true
+ deps=
+ dep1
+ """)
+ venv = mocksession.getenv('python')
+ venv.create()
+ l = mocksession._pcalls
+ assert len(l) == 1
+ l[:] = []
+
+ venv.install_deps()
+ assert len(l) == 1
+ args = " ".join(l[0].args)
+ assert "--pre " in args
+ assert "dep1" in args
+
def test_installpkg_indexserver(newmocksession, tmpdir):
mocksession = newmocksession([], """
[tox]
@@ -231,6 +258,20 @@ def test_install_recreate(newmocksession, tmpdir):
venv.update()
mocksession.report.expect("verbosity0", "*recreate*")
+def test_test_hashseed_is_in_output(newmocksession):
+ original_make_hashseed = tox._config.make_hashseed
+ tox._config.make_hashseed = lambda: '123456789'
+ try:
+ mocksession = newmocksession([], '''
+ [testenv]
+ ''')
+ finally:
+ tox._config.make_hashseed = original_make_hashseed
+ venv = mocksession.getenv('python')
+ venv.update()
+ venv.test()
+ mocksession.report.expect("verbosity0", "python runtests: PYTHONHASHSEED='123456789'")
+
def test_test_runtests_action_command_is_in_output(newmocksession):
mocksession = newmocksession([], '''
[testenv]
@@ -262,7 +303,7 @@ def test_install_command_not_installed(newmocksession, monkeypatch):
venv = mocksession.getenv('python')
venv.test()
mocksession.report.expect("warning", "*test command found but not*")
- assert venv.status == "commands failed"
+ assert venv.status == 0
def test_install_command_whitelisted(newmocksession, monkeypatch):
mocksession = newmocksession(['--recreate'], """
@@ -280,7 +321,7 @@ def test_install_command_whitelisted(newmocksession, monkeypatch):
assert venv.status == "commands failed"
@pytest.mark.skipif("not sys.platform.startswith('linux')")
-def test_install_command_not_installed(newmocksession):
+def test_install_command_not_installed_bash(newmocksession):
mocksession = newmocksession(['--recreate'], """
[testenv]
commands=
@@ -292,11 +333,11 @@ def test_install_command_not_installed(newmocksession):
def test_install_python3(tmpdir, newmocksession):
- if not py.path.local.sysfind('python3.1'):
- py.test.skip("needs python3.1")
+ if not py.path.local.sysfind('python3.3'):
+ pytest.skip("needs python3.3")
mocksession = newmocksession([], """
[testenv:py123]
- basepython=python3.1
+ basepython=python3.3
deps=
dep1
dep2
@@ -306,7 +347,7 @@ def test_install_python3(tmpdir, newmocksession):
l = mocksession._pcalls
assert len(l) == 1
args = l[0].args
- assert str(args[1]).endswith('virtualenv.py')
+ assert str(args[1]).endswith('virtualenv')
l[:] = []
action = mocksession.newaction(venv, "hello")
venv._install(["hello"], action=action)
@@ -372,7 +413,7 @@ class TestCreationConfig:
[testenv]
deps={distshare}/xyz-*
""")
- xyz = config.distshare.ensure("xyz-1.2.0.zip")
+ config.distshare.ensure("xyz-1.2.0.zip")
xyz2 = config.distshare.ensure("xyz-1.2.1.zip")
envconfig = config.envconfigs['python']
venv = VirtualEnv(envconfig, session=mocksession)
@@ -470,9 +511,11 @@ class TestVenvTest:
py.test.raises(ZeroDivisionError, "venv._pcall([1,2,3])")
monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1")
monkeypatch.setenv("PIP_REQUIRE_VIRTUALENV", "1")
+ monkeypatch.setenv("__PYVENV_LAUNCHER__", "1")
py.test.raises(ZeroDivisionError, "venv.run_install_command(['qwe'])")
assert 'PIP_RESPECT_VIRTUALENV' not in os.environ
assert 'PIP_REQUIRE_VIRTUALENV' not in os.environ
+ assert '__PYVENV_LAUNCHER__' not in os.environ
def test_setenv_added_to_pcall(tmpdir, mocksession, newconfig):
pkg = tmpdir.ensure("package.tar.gz")
@@ -492,11 +535,11 @@ def test_setenv_added_to_pcall(tmpdir, mocksession, newconfig):
l = mocksession._pcalls
assert len(l) == 2
for x in l:
- args = x.args
env = x.env
assert env is not None
assert 'ENV_VAR' in env
assert env['ENV_VAR'] == 'value'
+ assert env['VIRTUAL_ENV'] == str(venv.path)
for e in os.environ:
assert e in env
@@ -520,8 +563,10 @@ def test_installpkg_upgrade(newmocksession, tmpdir):
mocksession.installpkg(venv, pkg)
l = mocksession._pcalls
assert len(l) == 1
- assert '-U' in l[0].args
- assert '--no-deps' in l[0].args
+ index = l[0].args.index(str(pkg))
+ assert index >= 0
+ assert '-U' in l[0].args[:index]
+ assert '--no-deps' in l[0].args[:index]
def test_run_install_command(newmocksession):
mocksession = newmocksession([], "")
@@ -529,15 +574,13 @@ def test_run_install_command(newmocksession):
venv.just_created = True
venv.envconfig.envdir.ensure(dir=1)
action = mocksession.newaction(venv, "hello")
- venv.run_install_command(args=["whatever"], action=action)
+ venv.run_install_command(packages=["whatever"], action=action)
l = mocksession._pcalls
assert len(l) == 1
assert 'pip' in l[0].args[0]
assert 'install' in l[0].args
env = l[0].env
assert env is not None
- assert 'PYTHONIOENCODING' in env
- assert env['PYTHONIOENCODING'] == 'utf_8'
def test_run_custom_install_command(newmocksession):
mocksession = newmocksession([], """
@@ -548,7 +591,7 @@ def test_run_custom_install_command(newmocksession):
venv.just_created = True
venv.envconfig.envdir.ensure(dir=1)
action = mocksession.newaction(venv, "hello")
- venv.run_install_command(args=["whatever"], action=action)
+ venv.run_install_command(packages=["whatever"], action=action)
l = mocksession._pcalls
assert len(l) == 1
assert 'easy_install' in l[0].args[0]
@@ -568,6 +611,7 @@ def test_command_relative_issue26(newmocksession, tmpdir, monkeypatch):
mocksession.report.not_expect("warning", "*test command found but not*")
monkeypatch.setenv("PATH", str(tmpdir))
x4 = venv.getcommandpath("x", cwd=tmpdir)
+ assert x4.endswith(os.sep + 'x')
mocksession.report.expect("warning", "*test command found but not*")
def test_sethome_only_on_option(newmocksession, monkeypatch):
diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py
index 343c142..00188ac 100644
--- a/tests/test_z_cmdline.py
+++ b/tests/test_z_cmdline.py
@@ -1,7 +1,6 @@
import tox
import py
import pytest
-import sys
from tox._pytestplugin import ReportExpectMock
try:
import json
@@ -129,7 +128,6 @@ class TestSession:
})
config = parseconfig([])
session = Session(config)
- envlist = ['hello', 'world']
envs = session.venvlist
assert len(envs) == 2
env1, env2 = envs
@@ -193,6 +191,19 @@ def test_minversion(cmd, initproj):
])
assert result.ret
+def test_run_custom_install_command_error(cmd, initproj):
+ initproj("interp123-0.5", filedefs={
+ 'tox.ini': '''
+ [testenv]
+ install_command=./tox.ini {opts} {packages}
+ '''
+ })
+ result = cmd.run("tox")
+ result.stdout.fnmatch_lines([
+ "ERROR: invocation failed (errno *), args: ['*/tox.ini*",
+ ])
+ assert result.ret
+
def test_unknown_interpreter_and_env(cmd, initproj):
initproj("interp123-0.5", filedefs={
'tests': {'test_hello.py': "def test_hello(): pass"},
@@ -231,6 +242,22 @@ def test_unknown_interpreter(cmd, initproj):
"*ERROR*InterpreterNotFound*xyz_unknown_interpreter*",
])
+def test_skip_unknown_interpreter(cmd, initproj):
+ initproj("interp123-0.5", filedefs={
+ 'tests': {'test_hello.py': "def test_hello(): pass"},
+ 'tox.ini': '''
+ [testenv:python]
+ basepython=xyz_unknown_interpreter
+ [testenv]
+ changedir=tests
+ '''
+ })
+ result = cmd.run("tox", "--skip-missing-interpreters")
+ assert not result.ret
+ result.stdout.fnmatch_lines([
+ "*SKIPPED*InterpreterNotFound*xyz_unknown_interpreter*",
+ ])
+
def test_unknown_dep(cmd, initproj):
initproj("dep123-0.7", filedefs={
'tests': {'test_hello.py': "def test_hello(): pass"},
@@ -567,7 +594,7 @@ def test_sdistonly(initproj, cmd):
result.stdout.fnmatch_lines([
"*sdist-make*setup.py*",
])
- assert "virtualenv" not in result.stdout.str()
+ assert "-mvirtualenv" not in result.stdout.str()
def test_separate_sdist_no_sdistfile(cmd, initproj):
distshare = cmd.tmpdir.join("distshare")
@@ -582,6 +609,7 @@ def test_separate_sdist_no_sdistfile(cmd, initproj):
l = distshare.listdir()
assert len(l) == 1
sdistfile = l[0]
+ assert 'pkg123-0.7.zip' in str(sdistfile)
def test_separate_sdist(cmd, initproj):
distshare = cmd.tmpdir.join("distshare")
@@ -611,7 +639,6 @@ def test_sdist_latest(tmpdir, newconfig):
distshare=%s
sdistsrc={distshare}/pkg123-*
""" % distshare)
- p0 = distshare.ensure("pkg123-1.3.5.zip")
p = distshare.ensure("pkg123-1.4.5.zip")
distshare.ensure("pkg123-1.4.5a1.zip")
session = Session(config)
diff --git a/tox.ini b/tox.ini
index 40f532d..6bcca5e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist=py25,py27,py26,py32,py33,pypy
+envlist=py27,py26,py34,py33,py32,pypy,flakes
[testenv:X]
commands=echo {posargs}
@@ -18,9 +18,13 @@ commands=
--junitxml={envlogdir}/junit-{envname}.xml \
check_sphinx.py {posargs}
+[testenv:flakes]
+deps = pytest-flakes>=0.2
+commands = py.test --flakes -m flakes tox tests
+
[testenv:py25]
setenv= PIP_INSECURE=1
[pytest]
rsyncdirs=tests tox
-
+addopts = -rsxXf
diff --git a/tox/__init__.py b/tox/__init__.py
index 79b9cd3..6797068 100644
--- a/tox/__init__.py
+++ b/tox/__init__.py
@@ -1,5 +1,5 @@
#
-__version__ = '1.6.2.dev1'
+__version__ = '1.8.2.dev1'
class exception:
class Error(Exception):
@@ -20,4 +20,4 @@ class exception:
class MissingDependency(Error):
""" a dependency could not be found or determined. """
-from tox._cmdline import main as cmdline
+from tox._cmdline import main as cmdline # noqa
diff --git a/tox/__main__.py b/tox/__main__.py
new file mode 100644
index 0000000..d60aab2
--- /dev/null
+++ b/tox/__main__.py
@@ -0,0 +1,3 @@
+from tox._cmdline import main
+
+main()
diff --git a/tox/_cmdline.py b/tox/_cmdline.py
index 9e8ff9f..1c25f48 100644
--- a/tox/_cmdline.py
+++ b/tox/_cmdline.py
@@ -98,7 +98,13 @@ class Action(object):
if cwd is None:
# XXX cwd = self.session.config.cwd
cwd = py.path.local()
- popen = self._popen(args, cwd, env=env, stdout=stdout, stderr=STDOUT)
+ try:
+ popen = self._popen(args, cwd, env=env,
+ stdout=stdout, stderr=STDOUT)
+ except OSError as e:
+ self.report.error("invocation failed (errno %d), args: %s, cwd: %s" %
+ (e.errno, args, cwd))
+ raise
popen.outpath = outpath
popen.args = [str(x) for x in args]
popen.cwd = cwd
@@ -134,8 +140,8 @@ class Action(object):
if ret:
invoked = " ".join(map(str, popen.args))
if outpath:
- self.report.error("invocation failed, logfile: %s" %
- outpath)
+ self.report.error("invocation failed (exit code %d), logfile: %s" %
+ (ret, outpath))
out = outpath.read()
self.report.error(out)
if hasattr(self, "commandlog"):
@@ -171,6 +177,7 @@ class Action(object):
if env is None:
env = os.environ.copy()
return self.session.popen(args, shell=False, cwd=str(cwd),
+ universal_newlines=True,
stdout=stdout, stderr=stderr, env=env)
@@ -242,6 +249,9 @@ class Reporter(object):
def error(self, msg):
self.logline("ERROR: " + msg, red=True)
+ def skip(self, msg):
+ self.logline("SKIPPED:" + msg, yellow=True)
+
def logline(self, msg, **opts):
self._reportedlines.append(msg)
self.tw.line("%s" % msg, **opts)
@@ -430,14 +440,20 @@ class Session:
sdist_path = self._makesdist()
except tox.exception.InvocationError:
v = sys.exc_info()[1]
- self.report.error("FAIL could not package project")
+ self.report.error("FAIL could not package project - v = %r" %
+ v)
return
sdistfile = self.config.distshare.join(sdist_path.basename)
if sdistfile != sdist_path:
self.report.info("copying new sdistfile to %r" %
str(sdistfile))
- sdistfile.dirpath().ensure(dir=1)
- sdist_path.copy(sdistfile)
+ try:
+ sdistfile.dirpath().ensure(dir=1)
+ except py.error.Error:
+ self.report.warning("could not copy distfile to %s" %
+ sdistfile.dirpath())
+ else:
+ sdist_path.copy(sdistfile)
return sdist_path
def subcommand_test(self):
@@ -475,7 +491,14 @@ class Session:
retcode = 0
for venv in self.venvlist:
status = venv.status
- if status and status != "skipped tests":
+ if isinstance(status, tox.exception.InterpreterNotFound):
+ msg = " %s: %s" %(venv.envconfig.envname, str(status))
+ if self.config.option.skip_missing_interpreters:
+ self.report.skip(msg)
+ else:
+ retcode = 1
+ self.report.error(msg)
+ elif status and status != "skipped tests":
msg = " %s: %s" %(venv.envconfig.envname, str(status))
self.report.error(msg)
retcode = 1
@@ -576,7 +599,7 @@ class Session:
return candidates[0]
-_rex_getversion = py.std.re.compile("[\w_\-\+]+-(.*)(\.zip|\.tar.gz)")
+_rex_getversion = py.std.re.compile("[\w_\-\+\.]+-(.*)(\.zip|\.tar.gz)")
def getversion(basename):
m = _rex_getversion.match(basename)
if m is None:
diff --git a/tox/_config.py b/tox/_config.py
index 0529820..6a1f2c6 100644
--- a/tox/_config.py
+++ b/tox/_config.py
@@ -1,12 +1,12 @@
import argparse
-import distutils.sysconfig
import os
+import random
import sys
import re
import shlex
import string
-import subprocess
-import textwrap
+import pkg_resources
+import itertools
from tox.interpreters import Interpreters
@@ -14,14 +14,12 @@ import py
import tox
+iswin32 = sys.platform == "win32"
-defaultenvs = {'jython': 'jython', 'pypy': 'pypy'}
-for _name in "py,py24,py25,py26,py27,py30,py31,py32,py33,py34".split(","):
- if _name == "py":
- basepython = sys.executable
- else:
- basepython = "python" + ".".join(_name[2:4])
- defaultenvs[_name] = basepython
+default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3',
+ 'py': sys.executable}
+for version in '24,25,26,27,30,31,32,33,34'.split(','):
+ default_factors['py' + version] = 'python%s.%s' % tuple(version)
def parseconfig(args=None, pkg=None):
if args is None:
@@ -107,6 +105,9 @@ def prepare_parse(pkgname):
dest="indexurl", metavar="URL",
help="set indexserver url (if URL is of form name=url set the "
"url for the 'name' indexserver, specifically)")
+ parser.add_argument("--pre", action="store_true", dest="pre",
+ help="install pre-releases and development versions of dependencies. "
+ "This will pass the --pre option to install_command (pip by default).")
parser.add_argument("-r", "--recreate", action="store_true",
dest="recreate",
help="force recreation of virtual environments")
@@ -119,6 +120,23 @@ def prepare_parse(pkgname):
parser.add_argument("--result-tee", action="store_true",
dest="resulttee",
help="echo output of --result-json to stdout while it is captured.")
+ # We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED.
+ parser.add_argument("--hashseed", action="store",
+ metavar="SEED", default=None,
+ help="set PYTHONHASHSEED to SEED before running commands. "
+ "Defaults to a random integer in the range [1, 4294967295] "
+ "([1, 1024] on Windows). "
+ "Passing 'noset' suppresses this behavior.")
+ parser.add_argument("--force-dep", action="append",
+ metavar="REQ", default=None,
+ help="Forces a certain version of one of the dependencies "
+ "when configuring the virtual environment. REQ Examples "
+ "'pytest<2.7' or 'django>=1.6'.")
+ parser.add_argument("--sitepackages", action="store_true",
+ help="override sitepackages setting to True in all envs")
+ parser.add_argument("--skip-missing-interpreters", action="store_true",
+ help="don't fail tests for missing interpreters")
+
parser.add_argument("args", nargs="*",
help="additional arguments available to command positional substitution")
return parser
@@ -172,6 +190,12 @@ class VenvConfig:
info = self.config.interpreters.get_info(self.basepython)
if not info.executable:
raise tox.exception.InterpreterNotFound(self.basepython)
+ if not info.version_info:
+ raise tox.exception.InvocationError(
+ 'Failed to get version_info for %s: %s' % (info.name, info.err))
+ if info.version_info < (2,6):
+ raise tox.exception.UnsupportedInterpreter(
+ "python2.5 is not supported anymore, sorry")
return info.executable
testenvprefix = "testenv:"
@@ -182,10 +206,16 @@ def get_homedir():
except Exception:
return None
+def make_hashseed():
+ max_seed = 4294967295
+ if sys.platform == 'win32':
+ max_seed = 1024
+ return str(random.randint(1, max_seed))
+
class parseini:
def __init__(self, config, inipath):
config.toxinipath = inipath
- config.toxinidir = toxinidir = config.toxinipath.dirpath()
+ config.toxinidir = config.toxinipath.dirpath()
self._cfg = py.iniconfig.IniConfig(config.toxinipath)
config._cfg = self._cfg
@@ -202,6 +232,13 @@ class parseini:
else:
raise ValueError("invalid context")
+ if config.option.hashseed is None:
+ hashseed = make_hashseed()
+ elif config.option.hashseed == 'noset':
+ hashseed = None
+ else:
+ hashseed = config.option.hashseed
+ config.hashseed = hashseed
reader.addsubstitutions(toxinidir=config.toxinidir,
homedir=config.homedir)
@@ -209,10 +246,14 @@ class parseini:
"{toxinidir}/.tox")
config.minversion = reader.getdefault(toxsection, "minversion", None)
+ if not config.option.skip_missing_interpreters:
+ config.option.skip_missing_interpreters = \
+ reader.getbool(toxsection, "skip_missing_interpreters", False)
+
# determine indexserver dictionary
config.indexserver = {'default': IndexServerConfig('default')}
prefix = "indexserver"
- for line in reader.getlist(toxsection, "indexserver"):
+ for line in reader.getlist(toxsection, prefix):
name, url = map(lambda x: x.strip(), line.split("=", 1))
config.indexserver[name] = IndexServerConfig(name, url)
@@ -246,22 +287,19 @@ class parseini:
config.sdistsrc = reader.getpath(toxsection, "sdistsrc", None)
config.setupdir = reader.getpath(toxsection, "setupdir", "{toxinidir}")
config.logdir = config.toxworkdir.join("log")
- for sectionwrapper in self._cfg:
- section = sectionwrapper.name
- if section.startswith(testenvprefix):
- name = section[len(testenvprefix):]
- envconfig = self._makeenvconfig(name, section, reader._subs,
- config)
- config.envconfigs[name] = envconfig
- if not config.envconfigs:
- config.envconfigs['python'] = \
- self._makeenvconfig("python", "_xz_9", reader._subs, config)
- config.envlist = self._getenvlist(reader, toxsection)
- for name in config.envlist:
- if name not in config.envconfigs:
- if name in defaultenvs:
- config.envconfigs[name] = \
- self._makeenvconfig(name, "_xz_9", reader._subs, config)
+
+ config.envlist, all_envs = self._getenvdata(reader, toxsection)
+
+ # configure testenvs
+ known_factors = self._list_section_factors("testenv")
+ known_factors.update(default_factors)
+ known_factors.add("python")
+ for name in all_envs:
+ section = testenvprefix + name
+ factors = set(name.split('-'))
+ if section in self._cfg or factors <= known_factors:
+ config.envconfigs[name] = \
+ self._makeenvconfig(name, section, reader._subs, config)
all_develop = all(name in config.envconfigs
and config.envconfigs[name].develop
@@ -269,21 +307,30 @@ class parseini:
config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop)
+ def _list_section_factors(self, section):
+ factors = set()
+ if section in self._cfg:
+ for _, value in self._cfg[section].items():
+ exprs = re.findall(r'^([\w{}\.,-]+)\:\s+', value, re.M)
+ factors.update(*mapcat(_split_factor_expr, exprs))
+ return factors
+
def _makeenvconfig(self, name, section, subs, config):
vc = VenvConfig(envname=name)
vc.config = config
- reader = IniReader(self._cfg, fallbacksections=["testenv"])
+ factors = set(name.split('-'))
+ reader = IniReader(self._cfg, fallbacksections=["testenv"],
+ factors=factors)
reader.addsubstitutions(**subs)
- vc.develop = reader.getbool(section, "usedevelop", config.option.develop)
+ vc.develop = not config.option.installpkg and \
+ reader.getbool(section, "usedevelop", config.option.develop)
vc.envdir = reader.getpath(section, "envdir", "{toxworkdir}/%s" % name)
vc.args_are_paths = reader.getbool(section, "args_are_paths", True)
if reader.getdefault(section, "python", None):
raise tox.exception.ConfigError(
"'python=' key was renamed to 'basepython='")
- if name in defaultenvs:
- bp = defaultenvs[name]
- else:
- bp = sys.executable
+ bp = next((default_factors[f] for f in factors if f in default_factors),
+ sys.executable)
vc.basepython = reader.getdefault(section, "basepython", bp)
vc._basepython_info = config.interpreters.get_info(vc.basepython)
reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname,
@@ -307,7 +354,11 @@ class parseini:
arg = vc.changedir.bestrelpath(origpath)
args.append(arg)
reader.addsubstitutions(args)
- vc.setenv = reader.getdict(section, 'setenv')
+ setenv = {}
+ if config.hashseed is not None:
+ setenv['PYTHONHASHSEED'] = config.hashseed
+ setenv.update(reader.getdict(section, 'setenv'))
+ vc.setenv = setenv
if not vc.setenv:
vc.setenv = None
@@ -323,9 +374,12 @@ class parseini:
else:
name = depline.strip()
ixserver = None
+ name = self._replace_forced_dep(name, config)
vc.deps.append(DepConfig(name, ixserver))
vc.distribute = reader.getbool(section, "distribute", False)
- vc.sitepackages = reader.getbool(section, "sitepackages", False)
+ vc.sitepackages = self.config.option.sitepackages or \
+ reader.getbool(section, "sitepackages", False)
+
vc.downloadcache = None
downloadcache = reader.getdefault(section, "downloadcache")
if downloadcache:
@@ -333,49 +387,91 @@ class parseini:
downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", downloadcache)
vc.downloadcache = py.path.local(downloadcache)
- # on pip-1.3.1/python 2.5 we can't use "--pre".
- pip_default_opts = ["{opts}", "{packages}"]
- info = vc._basepython_info
- if info.runnable and info.version_info < (2,6):
- pass
- else:
- pip_default_opts.insert(0, "--pre")
vc.install_command = reader.getargv(
section,
"install_command",
- "pip install " + " ".join(pip_default_opts),
+ "pip install {opts} {packages}",
)
if '{packages}' not in vc.install_command:
raise tox.exception.ConfigError(
"'install_command' must contain '{packages}' substitution")
+ vc.pip_pre = config.option.pre or reader.getbool(
+ section, "pip_pre", False)
+
return vc
- def _getenvlist(self, reader, toxsection):
- env = self.config.option.env
- if not env:
- env = os.environ.get("TOXENV", None)
- if not env:
- envlist = reader.getlist(toxsection, "envlist", sep=",")
- if not envlist:
- envlist = self.config.envconfigs.keys()
- return envlist
- envlist = _split_env(env)
- if "ALL" in envlist:
- envlist = list(self.config.envconfigs)
- envlist.sort()
- return envlist
+ def _getenvdata(self, reader, toxsection):
+ envstr = self.config.option.env \
+ or os.environ.get("TOXENV") \
+ or reader.getdefault(toxsection, "envlist", replace=False) \
+ or []
+ envlist = _split_env(envstr)
+
+ # collect section envs
+ all_envs = set(envlist) - set(["ALL"])
+ for section in self._cfg:
+ if section.name.startswith(testenvprefix):
+ all_envs.add(section.name[len(testenvprefix):])
+ if not all_envs:
+ all_envs.add("python")
+
+ if not envlist or "ALL" in envlist:
+ envlist = sorted(all_envs)
+
+ return envlist, all_envs
+
+ def _replace_forced_dep(self, name, config):
+ """
+ Override the given dependency config name taking --force-dep-version
+ option into account.
+
+ :param name: dep config, for example ["pkg==1.0", "other==2.0"].
+ :param config: Config instance
+ :return: the new dependency that should be used for virtual environments
+ """
+ if not config.option.force_dep:
+ return name
+ for forced_dep in config.option.force_dep:
+ if self._is_same_dep(forced_dep, name):
+ return forced_dep
+ return name
+
+ @classmethod
+ def _is_same_dep(cls, dep1, dep2):
+ """
+ Returns True if both dependency definitions refer to the
+ same package, even if versions differ.
+ """
+ dep1_name = pkg_resources.Requirement.parse(dep1).project_name
+ dep2_name = pkg_resources.Requirement.parse(dep2).project_name
+ return dep1_name == dep2_name
+
def _split_env(env):
"""if handed a list, action="append" was used for -e """
- envlist = []
if not isinstance(env, list):
env = [env]
- for to_split in env:
- for single_env in to_split.split(","):
- # "remove True or", if not allowing multiple same runs, update tests
- if True or single_env not in envlist:
- envlist.append(single_env)
- return envlist
+ return mapcat(_expand_envstr, env)
+
+def _split_factor_expr(expr):
+ partial_envs = _expand_envstr(expr)
+ return [set(e.split('-')) for e in partial_envs]
+
+def _expand_envstr(envstr):
+ # split by commas not in groups
+ tokens = re.split(r'(\{[^}]+\})|,', envstr)
+ envlist = [''.join(g).strip()
+ for k, g in itertools.groupby(tokens, key=bool) if k]
+
+ def expand(env):
+ tokens = re.split(r'\{([^}]+)\}', env)
+ parts = [token.split(',') for token in tokens]
+ return [''.join(variant) for variant in itertools.product(*parts)]
+
+ return mapcat(expand, envlist)
+
+def mapcat(f, seq):
+ return list(itertools.chain.from_iterable(map(f, seq)))
class DepConfig:
def __init__(self, name, indexserver=None):
@@ -396,8 +492,8 @@ class IndexServerConfig:
self.url = url
RE_ITEM_REF = re.compile(
- '''
- [{]
+ r'''
+ (?<!\\)[{]
(?:(?P<sub_type>[^[:{}]+):)? # optional sub_type for special rules
(?P<substitution_value>[^{}]*) # substitution key
[}]
@@ -406,16 +502,17 @@ RE_ITEM_REF = re.compile(
class IniReader:
- def __init__(self, cfgparser, fallbacksections=None):
+ def __init__(self, cfgparser, fallbacksections=None, factors=()):
self._cfg = cfgparser
self.fallbacksections = fallbacksections or []
+ self.factors = factors
self._subs = {}
self._subststack = []
def addsubstitutions(self, _posargs=None, **kw):
self._subs.update(kw)
if _posargs:
- self._subs['_posargs'] = _posargs
+ self.posargs = _posargs
def getpath(self, section, name, defaultpath):
toxinidir = self._subs['toxinidir']
@@ -437,6 +534,8 @@ class IniReader:
value = {}
for line in s.split(sep):
+ if not line.strip():
+ continue
name, rest = line.split('=', 1)
value[name.strip()] = rest.strip()
@@ -470,33 +569,52 @@ class IniReader:
return commandlist
def _processcommand(self, command):
- posargs = self._subs.get('_posargs', None)
- words = list(CommandParser(command).words())
- new_command = ''
- for word in words:
- if word == '[]':
+ posargs = getattr(self, "posargs", None)
+
+ # Iterate through each word of the command substituting as
+ # appropriate to construct the new command string. This
+ # string is then broken up into exec argv components using
+ # shlex.
+ newcommand = ""
+ for word in CommandParser(command).words():
+ if word == "{posargs}" or word == "[]":
if posargs:
- new_command += ' '.join(posargs)
+ newcommand += " ".join(posargs)
continue
-
- new_word = self._replace(word, quote=True)
- # two passes; we might have substitutions in the result
- new_word = self._replace(new_word, quote=True)
- new_command += new_word
-
- return shlex.split(new_command.strip())
+ elif word.startswith("{posargs:") and word.endswith("}"):
+ if posargs:
+ newcommand += " ".join(posargs)
+ continue
+ else:
+ word = word[9:-1]
+ new_arg = ""
+ new_word = self._replace(word)
+ new_word = self._replace(new_word)
+ new_arg += new_word
+ newcommand += new_arg
+
+ # Construct shlex object that will not escape any values,
+ # use all values as is in argv.
+ shlexer = shlex.shlex(newcommand, posix=True)
+ shlexer.whitespace_split = True
+ shlexer.escape = ''
+ shlexer.commenters = ''
+ argv = list(shlexer)
+ return argv
def getargv(self, section, name, default=None, replace=True):
command = self.getdefault(
- section, name, default=default, replace=replace)
-
- return shlex.split(command.strip())
+ section, name, default=default, replace=False)
+ return self._processcommand(command.strip())
def getbool(self, section, name, default=None):
s = self.getdefault(section, name, default)
+ if not s:
+ s = default
if s is None:
raise KeyError("no config value [%s] %s found" % (
section, name))
+
if not isinstance(s, bool):
if s.lower() == "true":
s = True
@@ -508,18 +626,19 @@ class IniReader:
return s
def getdefault(self, section, name, default=None, replace=True):
- try:
- x = self._cfg[section][name]
- except KeyError:
- for fallbacksection in self.fallbacksections:
- try:
- x = self._cfg[fallbacksection][name]
- except KeyError:
- pass
- else:
- break
- else:
- x = default
+ x = None
+ for s in [section] + self.fallbacksections:
+ try:
+ x = self._cfg[s][name]
+ break
+ except KeyError:
+ continue
+
+ if x is None:
+ x = default
+ else:
+ x = self._apply_factors(x)
+
if replace and x and hasattr(x, 'replace'):
self._subststack.append((section, name))
try:
@@ -529,35 +648,41 @@ class IniReader:
#print "getdefault", section, name, "returned", repr(x)
return x
- def _replace_posargs(self, match, quote):
- return self._do_replace_posargs(lambda: match.group('substitution_value'))
-
- def _do_replace_posargs(self, value_func):
- posargs = self._subs.get('_posargs', None)
-
- if posargs:
- return " ".join(posargs)
+ def _apply_factors(self, s):
+ def factor_line(line):
+ m = re.search(r'^([\w{}\.,-]+)\:\s+(.+)', line)
+ if not m:
+ return line
- value = value_func()
- if value:
- return value
+ expr, line = m.groups()
+ if any(fs <= self.factors for fs in _split_factor_expr(expr)):
+ return line
- return ''
+ lines = s.strip().splitlines()
+ return '\n'.join(filter(None, map(factor_line, lines)))
- def _replace_env(self, match, quote):
- envkey = match.group('substitution_value')
- if not envkey:
+ def _replace_env(self, match):
+ match_value = match.group('substitution_value')
+ if not match_value:
raise tox.exception.ConfigError(
'env: requires an environment variable name')
- if not envkey in os.environ:
+ default = None
+ envkey_split = match_value.split(':', 1)
+
+ if len(envkey_split) is 2:
+ envkey, default = envkey_split
+ else:
+ envkey = match_value
+
+ if not envkey in os.environ and default is None:
raise tox.exception.ConfigError(
"substitution env:%r: unkown environment variable %r" %
(envkey, envkey))
- return os.environ[envkey]
+ return os.environ.get(envkey, default)
- def _substitute_from_other_section(self, key, quote):
+ def _substitute_from_other_section(self, key):
if key.startswith("[") and "]" in key:
i = key.find("]")
section, item = key[1:i], key[i+1:]
@@ -568,37 +693,25 @@ class IniReader:
x = str(self._cfg[section][item])
self._subststack.append((section, item))
try:
- return self._replace(x, quote=quote)
+ return self._replace(x)
finally:
self._subststack.pop()
raise tox.exception.ConfigError(
"substitution key %r not found" % key)
- def _replace_substitution(self, match, quote):
+ def _replace_substitution(self, match):
sub_key = match.group('substitution_value')
val = self._subs.get(sub_key, None)
if val is None:
- val = self._substitute_from_other_section(sub_key, quote)
+ val = self._substitute_from_other_section(sub_key)
if py.builtin.callable(val):
val = val()
- if quote:
- return '"%s"' % str(val).replace('"', r'\"')
- else:
- return str(val)
-
- def _is_bare_posargs(self, groupdict):
- return groupdict.get('substitution_value', None) == 'posargs' \
- and not groupdict.get('sub_type')
+ return str(val)
- def _replace_match(self, match, quote):
+ def _replace_match(self, match):
g = match.groupdict()
- # special case: posargs. If there is a 'posargs' substitution value
- # and no type, handle it as empty posargs
- if self._is_bare_posargs(g):
- return self._do_replace_posargs(lambda: '')
-
# special case: opts and packages. Leave {opts} and
# {packages} intact, they are replaced manually in
# _venv.VirtualEnv.run_install_command.
@@ -607,7 +720,6 @@ class IniReader:
return '{%s}' % sub_value
handlers = {
- 'posargs' : self._replace_posargs,
'env' : self._replace_env,
None : self._replace_substitution,
}
@@ -621,22 +733,11 @@ class IniReader:
except KeyError:
raise tox.exception.ConfigError("No support for the %s substitution type" % sub_type)
- # quoting is done in handlers, as at least posargs handling is special:
- # all of its arguments are inserted as separate parameters
- return handler(match, quote)
+ return handler(match)
- def _replace_match_quote(self, match):
- return self._replace_match(match, quote=True)
- def _replace_match_no_quote(self, match):
- return self._replace_match(match, quote=False)
-
- def _replace(self, x, quote=False):
+ def _replace(self, x):
if '{' in x:
- if quote:
- replace_func = self._replace_match_quote
- else:
- replace_func = self._replace_match_no_quote
- return RE_ITEM_REF.sub(replace_func, x)
+ return RE_ITEM_REF.sub(self._replace_match, x)
return x
def _parse_command(self, command):
@@ -659,7 +760,7 @@ class CommandParser(object):
def word_has_ended():
return ((cur_char in string.whitespace and ps.word and
ps.word[-1] not in string.whitespace) or
- (cur_char == '{' and ps.depth == 0) or
+ (cur_char == '{' and ps.depth == 0 and not ps.word.endswith('\\')) or
(ps.depth == 0 and ps.word and ps.word[-1] == '}') or
(cur_char not in string.whitespace and ps.word and
ps.word.strip() == ''))
@@ -704,6 +805,7 @@ class CommandParser(object):
return ps.yield_words
def getcontextname():
- if 'HUDSON_URL' in os.environ:
+ if any(env in os.environ for env in ['JENKINS_URL', 'HUDSON_URL']):
return 'jenkins'
return None
+
diff --git a/tox/_pytestplugin.py b/tox/_pytestplugin.py
index 6c90c8d..4f00d3f 100644
--- a/tox/_pytestplugin.py
+++ b/tox/_pytestplugin.py
@@ -16,10 +16,17 @@ def pytest_configure():
if 'HUDSON_URL' in os.environ:
del os.environ['HUDSON_URL']
+
+def pytest_addoption(parser):
+ parser.addoption("--no-network", action="store_true",
+ dest="no_network",
+ help="don't run tests requiring network")
+
def pytest_report_header():
return "tox comes from: %r" % (tox.__file__)
-def pytest_funcarg__newconfig(request, tmpdir):
+@pytest.fixture
+def newconfig(request, tmpdir):
def newconfig(args, source=None):
if source is None:
source = args
@@ -34,7 +41,10 @@ def pytest_funcarg__newconfig(request, tmpdir):
old.chdir()
return newconfig
-def pytest_funcarg__cmd(request):
+@pytest.fixture
+def cmd(request):
+ if request.config.option.no_network:
+ pytest.skip("--no-network was specified, test cannot run")
return Cmd(request)
class ReportExpectMock:
@@ -113,7 +123,8 @@ class pcallMock:
def wait(self):
pass
-def pytest_funcarg__mocksession(request):
+@pytest.fixture
+def mocksession(request):
from tox._cmdline import Session
class MockSession(Session):
def __init__(self):
@@ -130,13 +141,15 @@ def pytest_funcarg__mocksession(request):
def make_emptydir(self, path):
pass
def popen(self, args, cwd, shell=None,
+ universal_newlines=False,
stdout=None, stderr=None, env=None):
pm = pcallMock(args, cwd, env, stdout, stderr, shell)
self._pcalls.append(pm)
return pm
return MockSession()
-def pytest_funcarg__newmocksession(request):
+@pytest.fixture
+def newmocksession(request):
mocksession = request.getfuncargvalue("mocksession")
newconfig = request.getfuncargvalue("newconfig")
def newmocksession(args, source):
@@ -151,6 +164,7 @@ class Cmd:
self.request = request
current = py.path.local()
self.request.addfinalizer(current.chdir)
+
def chdir(self, target):
target.chdir()
diff --git a/tox/_quickstart.py b/tox/_quickstart.py
index 4b39d04..098eb61 100644
--- a/tox/_quickstart.py
+++ b/tox/_quickstart.py
@@ -56,7 +56,7 @@ except NameError:
term_input = input
-all_envs = ['py24', 'py25', 'py26', 'py27', 'py30', 'py31', 'py32', 'py33', 'pypy', 'jython']
+all_envs = ['py26', 'py27', 'py32', 'py33', 'py34', 'pypy', 'jython']
PROMPT_PREFIX = '> '
@@ -191,7 +191,7 @@ What command should be used to test your project -- examples:
default_deps = 'twisted'
print('''
-What dependencies does your project have?''')
+What extra dependencies do your tests have?''')
do_prompt(d, 'deps', 'Comma-separated list of dependencies', default_deps)
diff --git a/tox/_venv.py b/tox/_venv.py
index ec7ada2..937a881 100644
--- a/tox/_venv.py
+++ b/tox/_venv.py
@@ -1,6 +1,6 @@
from __future__ import with_statement
-import sys, os, re
-import subprocess
+import sys, os
+import codecs
import py
import tox
from tox._config import DepConfig
@@ -121,8 +121,6 @@ class VirtualEnv(object):
"""
if action is None:
action = self.session.newaction(self, "update")
- report = self.session.report
- name = self.envconfig.envname
rconfig = CreationConfig.readconfig(self.path_config)
if not self.envconfig.recreate and rconfig and \
rconfig.matches(self._getliveconfig()):
@@ -142,7 +140,8 @@ class VirtualEnv(object):
self.install_deps(action)
except tox.exception.InvocationError:
v = sys.exc_info()[1]
- return "could not install deps %s" %(self.envconfig.deps,)
+ return "could not install deps %s; v = %r" % (
+ self.envconfig.deps, v)
def _getliveconfig(self):
python = self.envconfig._basepython_info.executable
@@ -178,18 +177,8 @@ class VirtualEnv(object):
if action is None:
action = self.session.newaction(self, "create")
- interpreters = self.envconfig.config.interpreters
config_interpreter = self.getsupportedinterpreter()
- info = interpreters.get_info(executable=config_interpreter)
- use_venv191 = use_pip13 = info.version_info < (2,6)
- if not use_venv191:
- f, path, _ = py.std.imp.find_module("virtualenv")
- f.close()
- venvscript = path.rstrip("co")
- else:
- venvscript = py.path.local(tox.__file__).dirpath(
- "vendor", "virtualenv.py")
- args = [config_interpreter, str(venvscript)]
+ args = [sys.executable, '-mvirtualenv']
if self.envconfig.distribute:
args.append("--distribute")
else:
@@ -210,14 +199,7 @@ class VirtualEnv(object):
args.append(self.path.basename)
self._pcall(args, venv=False, action=action, cwd=basepath)
self.just_created = True
- if use_pip13:
- indexserver = self.envconfig.config.indexserver['default'].url
- action = self.session.newaction(self, "pip_downgrade")
- action.setactivity('pip-downgrade', 'pip<1.4')
- argv = ["easy_install"] + \
- self._installopts(indexserver) + ['pip<1.4']
- self._pcall(argv, cwd=self.envconfig.config.toxinidir,
- action=action)
+
def finish(self):
self._getliveconfig().writeconfig(self.path_config)
@@ -228,7 +210,7 @@ class VirtualEnv(object):
args = [self.envconfig.envpython, str(setup_py), '--name']
output = action.popen(args, cwd=setupdir, redirect=False,
returnout=True)
- name = output.strip().decode('utf-8')
+ name = output.strip()
egg_info = setupdir.join('.'.join((name, 'egg-info')))
for conf_file in (setup_py, setup_cfg):
if (not egg_info.check() or (conf_file.check()
@@ -278,29 +260,33 @@ class VirtualEnv(object):
if self.envconfig.downloadcache:
self.envconfig.downloadcache.ensure(dir=1)
l.append("--download-cache=%s" % self.envconfig.downloadcache)
+ if self.envconfig.pip_pre:
+ l.append("--pre")
return l
- def run_install_command(self, args, indexserver=None, action=None,
+ def run_install_command(self, packages, options=(),
+ indexserver=None, action=None,
extraenv=None):
argv = self.envconfig.install_command[:]
# use pip-script on win32 to avoid the executable locking
- if argv[0] == "pip" and sys.platform == "win32":
- argv[0] = "pip-script.py"
i = argv.index('{packages}')
- argv[i:i+1] = args
+ argv[i:i+1] = packages
if '{opts}' in argv:
i = argv.index('{opts}')
- argv[i:i+1] = self._installopts(indexserver)
- for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV'):
+ argv[i:i+1] = list(options)
+ for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV',
+ '__PYVENV_LAUNCHER__'):
try:
del os.environ[x]
except KeyError:
pass
- env = dict(PYTHONIOENCODING='utf_8')
- if extraenv is not None:
- env.update(extraenv)
+ old_stdout = sys.stdout
+ sys.stdout = codecs.getwriter('utf8')(sys.stdout)
+ if extraenv is None:
+ extraenv = {}
self._pcall(argv, cwd=self.envconfig.config.toxinidir,
- extraenv=env, action=action)
+ extraenv=extraenv, action=action)
+ sys.stdout = old_stdout
def _install(self, deps, extraopts=None, action=None):
if not deps:
@@ -320,7 +306,6 @@ class VirtualEnv(object):
l.append(ixserver)
assert ixserver.url is None or isinstance(ixserver.url, str)
- extraopts = extraopts or []
for ixserver in l:
if self.envconfig.config.option.sethome:
extraenv = hack_home_env(
@@ -329,18 +314,21 @@ class VirtualEnv(object):
else:
extraenv = {}
- args = d[ixserver] + extraopts
- self.run_install_command(args, ixserver.url, action,
- extraenv=extraenv)
-
- def _getenv(self):
- env = self.envconfig.setenv
- if env:
- env_arg = os.environ.copy()
- env_arg.update(env)
- else:
- env_arg = None
- return env_arg
+ packages = d[ixserver]
+ options = self._installopts(ixserver.url)
+ if extraopts:
+ options.extend(extraopts)
+ self.run_install_command(packages=packages, options=options,
+ action=action, extraenv=extraenv)
+
+ def _getenv(self, extraenv={}):
+ env = os.environ.copy()
+ setenv = self.envconfig.setenv
+ if setenv:
+ env.update(setenv)
+ env['VIRTUAL_ENV'] = str(self.path)
+ env.update(extraenv)
+ return env
def test(self, redirect=False):
action = self.session.newaction(self, "runtests")
@@ -348,6 +336,9 @@ class VirtualEnv(object):
self.status = 0
self.session.make_emptydir(self.envconfig.envtmpdir)
cwd = self.envconfig.changedir
+ env = self._getenv()
+ # Display PYTHONHASHSEED to assist with reproducibility.
+ action.setactivity("runtests", "PYTHONHASHSEED=%r" % env.get('PYTHONHASHSEED'))
for i, argv in enumerate(self.envconfig.commands):
# have to make strings as _pcall changes argv[0] to a local()
# happens if the same environment is invoked twice
@@ -377,8 +368,7 @@ class VirtualEnv(object):
old = self.patchPATH()
try:
args[0] = self.getcommandpath(args[0], venv, cwd)
- env = self._getenv() or os.environ.copy()
- env.update(extraenv)
+ env = self._getenv(extraenv)
return action.popen(args, cwd=cwd, env=env, redirect=redirect)
finally:
os.environ['PATH'] = old
diff --git a/tox/_verlib.py b/tox/_verlib.py
index 1df3645..a234176 100644
--- a/tox/_verlib.py
+++ b/tox/_verlib.py
@@ -8,7 +8,6 @@ licensed under the PSF license (i guess)
"""
-import sys
import re
class IrrationalVersionError(Exception):
diff --git a/tox/interpreters.py b/tox/interpreters.py
index 514ea39..75318c5 100644
--- a/tox/interpreters.py
+++ b/tox/interpreters.py
@@ -1,8 +1,6 @@
import sys
-import os
import py
import re
-import subprocess
import inspect
class Interpreters:
@@ -12,7 +10,7 @@ class Interpreters:
def get_executable(self, name):
""" return path object to the executable for the given
- name (e.g. python2.5, python2.7, python etc.)
+ name (e.g. python2.6, python2.7, python etc.)
if name is already an existing path, return name.
If an interpreter cannot be found, return None.
"""
@@ -161,7 +159,7 @@ else:
# The standard executables can be found as a last resort via the
# Python launcher py.exe
if m:
- locate_via_py(*m.groups())
+ return locate_via_py(*m.groups())
def pyinfo():
import sys
diff --git a/tox/result.py b/tox/result.py
index 694138c..9954044 100644
--- a/tox/result.py
+++ b/tox/result.py
@@ -1,9 +1,7 @@
import sys
import py
-try:
- import json
-except ImportError:
- import simplejson as json
+from tox import __version__ as toxver
+import json
class ResultLog:
@@ -11,12 +9,11 @@ class ResultLog:
if dict is None:
dict = {}
self.dict = dict
-
- def set_header(self, installpkg):
- from tox import __version__ as toxver
self.dict.update({"reportversion": "1", "toxversion": toxver})
self.dict["platform"] = sys.platform
self.dict["host"] = py.std.socket.getfqdn()
+
+ def set_header(self, installpkg):
self.dict["installpkg"] = dict(
md5=installpkg.computehash("md5"),
sha256=installpkg.computehash("sha256"),
diff --git a/tox/vendor/virtualenv.py b/tox/vendor/virtualenv.py
deleted file mode 100755
index ccb6eec..0000000
--- a/tox/vendor/virtualenv.py
+++ /dev/null
@@ -1,2581 +0,0 @@
-#!/usr/bin/env python
-"""Create a "virtual" Python installation
-"""
-
-# If you change the version here, change it in setup.py
-# and docs/conf.py as well.
-__version__ = "1.9.1" # following best practices
-virtualenv_version = __version__ # legacy, again
-
-import base64
-import sys
-import os
-import codecs
-import optparse
-import re
-import shutil
-import logging
-import tempfile
-import zlib
-import errno
-import glob
-import distutils.sysconfig
-from distutils.util import strtobool
-import struct
-import subprocess
-
-if sys.version_info < (2, 5):
- print('ERROR: %s' % sys.exc_info()[1])
- print('ERROR: this script requires Python 2.5 or greater.')
- sys.exit(101)
-
-try:
- set
-except NameError:
- from sets import Set as set
-try:
- basestring
-except NameError:
- basestring = str
-
-try:
- import ConfigParser
-except ImportError:
- import configparser as ConfigParser
-
-join = os.path.join
-py_version = 'python%s.%s' % (sys.version_info[0], sys.version_info[1])
-
-is_jython = sys.platform.startswith('java')
-is_pypy = hasattr(sys, 'pypy_version_info')
-is_win = (sys.platform == 'win32')
-is_cygwin = (sys.platform == 'cygwin')
-is_darwin = (sys.platform == 'darwin')
-abiflags = getattr(sys, 'abiflags', '')
-
-user_dir = os.path.expanduser('~')
-if is_win:
- default_storage_dir = os.path.join(user_dir, 'virtualenv')
-else:
- default_storage_dir = os.path.join(user_dir, '.virtualenv')
-default_config_file = os.path.join(default_storage_dir, 'virtualenv.ini')
-
-if is_pypy:
- expected_exe = 'pypy'
-elif is_jython:
- expected_exe = 'jython'
-else:
- expected_exe = 'python'
-
-
-REQUIRED_MODULES = ['os', 'posix', 'posixpath', 'nt', 'ntpath', 'genericpath',
- 'fnmatch', 'locale', 'encodings', 'codecs',
- 'stat', 'UserDict', 'readline', 'copy_reg', 'types',
- 're', 'sre', 'sre_parse', 'sre_constants', 'sre_compile',
- 'zlib']
-
-REQUIRED_FILES = ['lib-dynload', 'config']
-
-majver, minver = sys.version_info[:2]
-if majver == 2:
- if minver >= 6:
- REQUIRED_MODULES.extend(['warnings', 'linecache', '_abcoll', 'abc'])
- if minver >= 7:
- REQUIRED_MODULES.extend(['_weakrefset'])
- if minver <= 3:
- REQUIRED_MODULES.extend(['sets', '__future__'])
-elif majver == 3:
- # Some extra modules are needed for Python 3, but different ones
- # for different versions.
- REQUIRED_MODULES.extend(['_abcoll', 'warnings', 'linecache', 'abc', 'io',
- '_weakrefset', 'copyreg', 'tempfile', 'random',
- '__future__', 'collections', 'keyword', 'tarfile',
- 'shutil', 'struct', 'copy', 'tokenize', 'token',
- 'functools', 'heapq', 'bisect', 'weakref',
- 'reprlib'])
- if minver >= 2:
- REQUIRED_FILES[-1] = 'config-%s' % majver
- if minver == 3:
- import sysconfig
- platdir = sysconfig.get_config_var('PLATDIR')
- REQUIRED_FILES.append(platdir)
- # The whole list of 3.3 modules is reproduced below - the current
- # uncommented ones are required for 3.3 as of now, but more may be
- # added as 3.3 development continues.
- REQUIRED_MODULES.extend([
- #"aifc",
- #"antigravity",
- #"argparse",
- #"ast",
- #"asynchat",
- #"asyncore",
- "base64",
- #"bdb",
- #"binhex",
- #"bisect",
- #"calendar",
- #"cgi",
- #"cgitb",
- #"chunk",
- #"cmd",
- #"codeop",
- #"code",
- #"colorsys",
- #"_compat_pickle",
- #"compileall",
- #"concurrent",
- #"configparser",
- #"contextlib",
- #"cProfile",
- #"crypt",
- #"csv",
- #"ctypes",
- #"curses",
- #"datetime",
- #"dbm",
- #"decimal",
- #"difflib",
- #"dis",
- #"doctest",
- #"dummy_threading",
- "_dummy_thread",
- #"email",
- #"filecmp",
- #"fileinput",
- #"formatter",
- #"fractions",
- #"ftplib",
- #"functools",
- #"getopt",
- #"getpass",
- #"gettext",
- #"glob",
- #"gzip",
- "hashlib",
- #"heapq",
- "hmac",
- #"html",
- #"http",
- #"idlelib",
- #"imaplib",
- #"imghdr",
- "imp",
- "importlib",
- #"inspect",
- #"json",
- #"lib2to3",
- #"logging",
- #"macpath",
- #"macurl2path",
- #"mailbox",
- #"mailcap",
- #"_markupbase",
- #"mimetypes",
- #"modulefinder",
- #"multiprocessing",
- #"netrc",
- #"nntplib",
- #"nturl2path",
- #"numbers",
- #"opcode",
- #"optparse",
- #"os2emxpath",
- #"pdb",
- #"pickle",
- #"pickletools",
- #"pipes",
- #"pkgutil",
- #"platform",
- #"plat-linux2",
- #"plistlib",
- #"poplib",
- #"pprint",
- #"profile",
- #"pstats",
- #"pty",
- #"pyclbr",
- #"py_compile",
- #"pydoc_data",
- #"pydoc",
- #"_pyio",
- #"queue",
- #"quopri",
- #"reprlib",
- "rlcompleter",
- #"runpy",
- #"sched",
- #"shelve",
- #"shlex",
- #"smtpd",
- #"smtplib",
- #"sndhdr",
- #"socket",
- #"socketserver",
- #"sqlite3",
- #"ssl",
- #"stringprep",
- #"string",
- #"_strptime",
- #"subprocess",
- #"sunau",
- #"symbol",
- #"symtable",
- #"sysconfig",
- #"tabnanny",
- #"telnetlib",
- #"test",
- #"textwrap",
- #"this",
- #"_threading_local",
- #"threading",
- #"timeit",
- #"tkinter",
- #"tokenize",
- #"token",
- #"traceback",
- #"trace",
- #"tty",
- #"turtledemo",
- #"turtle",
- #"unittest",
- #"urllib",
- #"uuid",
- #"uu",
- #"wave",
- #"weakref",
- #"webbrowser",
- #"wsgiref",
- #"xdrlib",
- #"xml",
- #"xmlrpc",
- #"zipfile",
- ])
-
-if is_pypy:
- # these are needed to correctly display the exceptions that may happen
- # during the bootstrap
- REQUIRED_MODULES.extend(['traceback', 'linecache'])
-
-class Logger(object):
-
- """
- Logging object for use in command-line script. Allows ranges of
- levels, to avoid some redundancy of displayed information.
- """
-
- DEBUG = logging.DEBUG
- INFO = logging.INFO
- NOTIFY = (logging.INFO+logging.WARN)/2
- WARN = WARNING = logging.WARN
- ERROR = logging.ERROR
- FATAL = logging.FATAL
-
- LEVELS = [DEBUG, INFO, NOTIFY, WARN, ERROR, FATAL]
-
- def __init__(self, consumers):
- self.consumers = consumers
- self.indent = 0
- self.in_progress = None
- self.in_progress_hanging = False
-
- def debug(self, msg, *args, **kw):
- self.log(self.DEBUG, msg, *args, **kw)
- def info(self, msg, *args, **kw):
- self.log(self.INFO, msg, *args, **kw)
- def notify(self, msg, *args, **kw):
- self.log(self.NOTIFY, msg, *args, **kw)
- def warn(self, msg, *args, **kw):
- self.log(self.WARN, msg, *args, **kw)
- def error(self, msg, *args, **kw):
- self.log(self.ERROR, msg, *args, **kw)
- def fatal(self, msg, *args, **kw):
- self.log(self.FATAL, msg, *args, **kw)
- def log(self, level, msg, *args, **kw):
- if args:
- if kw:
- raise TypeError(
- "You may give positional or keyword arguments, not both")
- args = args or kw
- rendered = None
- for consumer_level, consumer in self.consumers:
- if self.level_matches(level, consumer_level):
- if (self.in_progress_hanging
- and consumer in (sys.stdout, sys.stderr)):
- self.in_progress_hanging = False
- sys.stdout.write('\n')
- sys.stdout.flush()
- if rendered is None:
- if args:
- rendered = msg % args
- else:
- rendered = msg
- rendered = ' '*self.indent + rendered
- if hasattr(consumer, 'write'):
- consumer.write(rendered+'\n')
- else:
- consumer(rendered)
-
- def start_progress(self, msg):
- assert not self.in_progress, (
- "Tried to start_progress(%r) while in_progress %r"
- % (msg, self.in_progress))
- if self.level_matches(self.NOTIFY, self._stdout_level()):
- sys.stdout.write(msg)
- sys.stdout.flush()
- self.in_progress_hanging = True
- else:
- self.in_progress_hanging = False
- self.in_progress = msg
-
- def end_progress(self, msg='done.'):
- assert self.in_progress, (
- "Tried to end_progress without start_progress")
- if self.stdout_level_matches(self.NOTIFY):
- if not self.in_progress_hanging:
- # Some message has been printed out since start_progress
- sys.stdout.write('...' + self.in_progress + msg + '\n')
- sys.stdout.flush()
- else:
- sys.stdout.write(msg + '\n')
- sys.stdout.flush()
- self.in_progress = None
- self.in_progress_hanging = False
-
- def show_progress(self):
- """If we are in a progress scope, and no log messages have been
- shown, write out another '.'"""
- if self.in_progress_hanging:
- sys.stdout.write('.')
- sys.stdout.flush()
-
- def stdout_level_matches(self, level):
- """Returns true if a message at this level will go to stdout"""
- return self.level_matches(level, self._stdout_level())
-
- def _stdout_level(self):
- """Returns the level that stdout runs at"""
- for level, consumer in self.consumers:
- if consumer is sys.stdout:
- return level
- return self.FATAL
-
- def level_matches(self, level, consumer_level):
- """
- >>> l = Logger([])
- >>> l.level_matches(3, 4)
- False
- >>> l.level_matches(3, 2)
- True
- >>> l.level_matches(slice(None, 3), 3)
- False
- >>> l.level_matches(slice(None, 3), 2)
- True
- >>> l.level_matches(slice(1, 3), 1)
- True
- >>> l.level_matches(slice(2, 3), 1)
- False
- """
- if isinstance(level, slice):
- start, stop = level.start, level.stop
- if start is not None and start > consumer_level:
- return False
- if stop is not None and stop <= consumer_level:
- return False
- return True
- else:
- return level >= consumer_level
-
- #@classmethod
- def level_for_integer(cls, level):
- levels = cls.LEVELS
- if level < 0:
- return levels[0]
- if level >= len(levels):
- return levels[-1]
- return levels[level]
-
- level_for_integer = classmethod(level_for_integer)
-
-# create a silent logger just to prevent this from being undefined
-# will be overridden with requested verbosity main() is called.
-logger = Logger([(Logger.LEVELS[-1], sys.stdout)])
-
-def mkdir(path):
- if not os.path.exists(path):
- logger.info('Creating %s', path)
- os.makedirs(path)
- else:
- logger.info('Directory %s already exists', path)
-
-def copyfileordir(src, dest):
- if os.path.isdir(src):
- shutil.copytree(src, dest, True)
- else:
- shutil.copy2(src, dest)
-
-def copyfile(src, dest, symlink=True):
- if not os.path.exists(src):
- # Some bad symlink in the src
- logger.warn('Cannot find file %s (bad symlink)', src)
- return
- if os.path.exists(dest):
- logger.debug('File %s already exists', dest)
- return
- if not os.path.exists(os.path.dirname(dest)):
- logger.info('Creating parent directories for %s' % os.path.dirname(dest))
- os.makedirs(os.path.dirname(dest))
- if not os.path.islink(src):
- srcpath = os.path.abspath(src)
- else:
- srcpath = os.readlink(src)
- if symlink and hasattr(os, 'symlink') and not is_win:
- logger.info('Symlinking %s', dest)
- try:
- os.symlink(srcpath, dest)
- except (OSError, NotImplementedError):
- logger.info('Symlinking failed, copying to %s', dest)
- copyfileordir(src, dest)
- else:
- logger.info('Copying to %s', dest)
- copyfileordir(src, dest)
-
-def writefile(dest, content, overwrite=True):
- if not os.path.exists(dest):
- logger.info('Writing %s', dest)
- f = open(dest, 'wb')
- f.write(content.encode('utf-8'))
- f.close()
- return
- else:
- f = open(dest, 'rb')
- c = f.read()
- f.close()
- if c != content.encode("utf-8"):
- if not overwrite:
- logger.notify('File %s exists with different content; not overwriting', dest)
- return
- logger.notify('Overwriting %s with new content', dest)
- f = open(dest, 'wb')
- f.write(content.encode('utf-8'))
- f.close()
- else:
- logger.info('Content %s already in place', dest)
-
-def rmtree(dir):
- if os.path.exists(dir):
- logger.notify('Deleting tree %s', dir)
- shutil.rmtree(dir)
- else:
- logger.info('Do not need to delete %s; already gone', dir)
-
-def make_exe(fn):
- if hasattr(os, 'chmod'):
- oldmode = os.stat(fn).st_mode & 0xFFF # 0o7777
- newmode = (oldmode | 0x16D) & 0xFFF # 0o555, 0o7777
- os.chmod(fn, newmode)
- logger.info('Changed mode of %s to %s', fn, oct(newmode))
-
-def _find_file(filename, dirs):
- for dir in reversed(dirs):
- files = glob.glob(os.path.join(dir, filename))
- if files and os.path.isfile(files[0]):
- return True, files[0]
- return False, filename
-
-def _install_req(py_executable, unzip=False, distribute=False,
- search_dirs=None, never_download=False):
-
- if search_dirs is None:
- search_dirs = file_search_dirs()
-
- if not distribute:
- egg_path = 'setuptools-*-py%s.egg' % sys.version[:3]
- found, egg_path = _find_file(egg_path, search_dirs)
- project_name = 'setuptools'
- bootstrap_script = EZ_SETUP_PY
- tgz_path = None
- else:
- # Look for a distribute egg (these are not distributed by default,
- # but can be made available by the user)
- egg_path = 'distribute-*-py%s.egg' % sys.version[:3]
- found, egg_path = _find_file(egg_path, search_dirs)
- project_name = 'distribute'
- if found:
- tgz_path = None
- bootstrap_script = DISTRIBUTE_FROM_EGG_PY
- else:
- # Fall back to sdist
- # NB: egg_path is not None iff tgz_path is None
- # iff bootstrap_script is a generic setup script accepting
- # the standard arguments.
- egg_path = None
- tgz_path = 'distribute-*.tar.gz'
- found, tgz_path = _find_file(tgz_path, search_dirs)
- bootstrap_script = DISTRIBUTE_SETUP_PY
-
- if is_jython and os._name == 'nt':
- # Jython's .bat sys.executable can't handle a command line
- # argument with newlines
- fd, ez_setup = tempfile.mkstemp('.py')
- os.write(fd, bootstrap_script)
- os.close(fd)
- cmd = [py_executable, ez_setup]
- else:
- cmd = [py_executable, '-c', bootstrap_script]
- if unzip and egg_path:
- cmd.append('--always-unzip')
- env = {}
- remove_from_env = ['__PYVENV_LAUNCHER__']
- if logger.stdout_level_matches(logger.DEBUG) and egg_path:
- cmd.append('-v')
-
- old_chdir = os.getcwd()
- if egg_path is not None and os.path.exists(egg_path):
- logger.info('Using existing %s egg: %s' % (project_name, egg_path))
- cmd.append(egg_path)
- if os.environ.get('PYTHONPATH'):
- env['PYTHONPATH'] = egg_path + os.path.pathsep + os.environ['PYTHONPATH']
- else:
- env['PYTHONPATH'] = egg_path
- elif tgz_path is not None and os.path.exists(tgz_path):
- # Found a tgz source dist, let's chdir
- logger.info('Using existing %s egg: %s' % (project_name, tgz_path))
- os.chdir(os.path.dirname(tgz_path))
- # in this case, we want to be sure that PYTHONPATH is unset (not
- # just empty, really unset), else CPython tries to import the
- # site.py that it's in virtualenv_support
- remove_from_env.append('PYTHONPATH')
- elif never_download:
- logger.fatal("Can't find any local distributions of %s to install "
- "and --never-download is set. Either re-run virtualenv "
- "without the --never-download option, or place a %s "
- "distribution (%s) in one of these "
- "locations: %r" % (project_name, project_name,
- egg_path or tgz_path,
- search_dirs))
- sys.exit(1)
- elif egg_path:
- logger.info('No %s egg found; downloading' % project_name)
- cmd.extend(['--always-copy', '-U', project_name])
- else:
- logger.info('No %s tgz found; downloading' % project_name)
- logger.start_progress('Installing %s...' % project_name)
- logger.indent += 2
- cwd = None
- if project_name == 'distribute':
- env['DONT_PATCH_SETUPTOOLS'] = 'true'
-
- def _filter_ez_setup(line):
- return filter_ez_setup(line, project_name)
-
- if not os.access(os.getcwd(), os.W_OK):
- cwd = tempfile.mkdtemp()
- if tgz_path is not None and os.path.exists(tgz_path):
- # the current working dir is hostile, let's copy the
- # tarball to a temp dir
- target = os.path.join(cwd, os.path.split(tgz_path)[-1])
- shutil.copy(tgz_path, target)
- try:
- call_subprocess(cmd, show_stdout=False,
- filter_stdout=_filter_ez_setup,
- extra_env=env,
- remove_from_env=remove_from_env,
- cwd=cwd)
- finally:
- logger.indent -= 2
- logger.end_progress()
- if cwd is not None:
- shutil.rmtree(cwd)
- if os.getcwd() != old_chdir:
- os.chdir(old_chdir)
- if is_jython and os._name == 'nt':
- os.remove(ez_setup)
-
-def file_search_dirs():
- here = os.path.dirname(os.path.abspath(__file__))
- dirs = ['.', here,
- join(here, 'virtualenv_support')]
- if os.path.splitext(os.path.dirname(__file__))[0] != 'virtualenv':
- # Probably some boot script; just in case virtualenv is installed...
- try:
- import virtualenv
- except ImportError:
- pass
- else:
- dirs.append(os.path.join(os.path.dirname(virtualenv.__file__), 'virtualenv_support'))
- return [d for d in dirs if os.path.isdir(d)]
-
-def install_setuptools(py_executable, unzip=False,
- search_dirs=None, never_download=False):
- _install_req(py_executable, unzip,
- search_dirs=search_dirs, never_download=never_download)
-
-def install_distribute(py_executable, unzip=False,
- search_dirs=None, never_download=False):
- _install_req(py_executable, unzip, distribute=True,
- search_dirs=search_dirs, never_download=never_download)
-
-_pip_re = re.compile(r'^pip-.*(zip|tar.gz|tar.bz2|tgz|tbz)$', re.I)
-def install_pip(py_executable, search_dirs=None, never_download=False):
- if search_dirs is None:
- search_dirs = file_search_dirs()
-
- filenames = []
- for dir in search_dirs:
- filenames.extend([join(dir, fn) for fn in os.listdir(dir)
- if _pip_re.search(fn)])
- filenames = [(os.path.basename(filename).lower(), i, filename) for i, filename in enumerate(filenames)]
- filenames.sort()
- filenames = [filename for basename, i, filename in filenames]
- if not filenames:
- filename = 'pip'
- else:
- filename = filenames[-1]
- easy_install_script = 'easy_install'
- if is_win:
- easy_install_script = 'easy_install-script.py'
- # There's two subtle issues here when invoking easy_install.
- # 1. On unix-like systems the easy_install script can *only* be executed
- # directly if its full filesystem path is no longer than 78 characters.
- # 2. A work around to [1] is to use the `python path/to/easy_install foo`
- # pattern, but that breaks if the path contains non-ASCII characters, as
- # you can't put the file encoding declaration before the shebang line.
- # The solution is to use Python's -x flag to skip the first line of the
- # script (and any ASCII decoding errors that may have occurred in that line)
- cmd = [py_executable, '-x', join(os.path.dirname(py_executable), easy_install_script), filename]
- # jython and pypy don't yet support -x
- if is_jython or is_pypy:
- cmd.remove('-x')
- if filename == 'pip':
- if never_download:
- logger.fatal("Can't find any local distributions of pip to install "
- "and --never-download is set. Either re-run virtualenv "
- "without the --never-download option, or place a pip "
- "source distribution (zip/tar.gz/tar.bz2) in one of these "
- "locations: %r" % search_dirs)
- sys.exit(1)
- logger.info('Installing pip from network...')
- else:
- logger.info('Installing existing %s distribution: %s' % (
- os.path.basename(filename), filename))
- logger.start_progress('Installing pip...')
- logger.indent += 2
- def _filter_setup(line):
- return filter_ez_setup(line, 'pip')
- try:
- call_subprocess(cmd, show_stdout=False,
- filter_stdout=_filter_setup)
- finally:
- logger.indent -= 2
- logger.end_progress()
-
-def filter_ez_setup(line, project_name='setuptools'):
- if not line.strip():
- return Logger.DEBUG
- if project_name == 'distribute':
- for prefix in ('Extracting', 'Now working', 'Installing', 'Before',
- 'Scanning', 'Setuptools', 'Egg', 'Already',
- 'running', 'writing', 'reading', 'installing',
- 'creating', 'copying', 'byte-compiling', 'removing',
- 'Processing'):
- if line.startswith(prefix):
- return Logger.DEBUG
- return Logger.DEBUG
- for prefix in ['Reading ', 'Best match', 'Processing setuptools',
- 'Copying setuptools', 'Adding setuptools',
- 'Installing ', 'Installed ']:
- if line.startswith(prefix):
- return Logger.DEBUG
- return Logger.INFO
-
-
-class UpdatingDefaultsHelpFormatter(optparse.IndentedHelpFormatter):
- """
- Custom help formatter for use in ConfigOptionParser that updates
- the defaults before expanding them, allowing them to show up correctly
- in the help listing
- """
- def expand_default(self, option):
- if self.parser is not None:
- self.parser.update_defaults(self.parser.defaults)
- return optparse.IndentedHelpFormatter.expand_default(self, option)
-
-
-class ConfigOptionParser(optparse.OptionParser):
- """
- Custom option parser which updates its defaults by by checking the
- configuration files and environmental variables
- """
- def __init__(self, *args, **kwargs):
- self.config = ConfigParser.RawConfigParser()
- self.files = self.get_config_files()
- self.config.read(self.files)
- optparse.OptionParser.__init__(self, *args, **kwargs)
-
- def get_config_files(self):
- config_file = os.environ.get('VIRTUALENV_CONFIG_FILE', False)
- if config_file and os.path.exists(config_file):
- return [config_file]
- return [default_config_file]
-
- def update_defaults(self, defaults):
- """
- Updates the given defaults with values from the config files and
- the environ. Does a little special handling for certain types of
- options (lists).
- """
- # Then go and look for the other sources of configuration:
- config = {}
- # 1. config files
- config.update(dict(self.get_config_section('virtualenv')))
- # 2. environmental variables
- config.update(dict(self.get_environ_vars()))
- # Then set the options with those values
- for key, val in config.items():
- key = key.replace('_', '-')
- if not key.startswith('--'):
- key = '--%s' % key # only prefer long opts
- option = self.get_option(key)
- if option is not None:
- # ignore empty values
- if not val:
- continue
- # handle multiline configs
- if option.action == 'append':
- val = val.split()
- else:
- option.nargs = 1
- if option.action == 'store_false':
- val = not strtobool(val)
- elif option.action in ('store_true', 'count'):
- val = strtobool(val)
- try:
- val = option.convert_value(key, val)
- except optparse.OptionValueError:
- e = sys.exc_info()[1]
- print("An error occured during configuration: %s" % e)
- sys.exit(3)
- defaults[option.dest] = val
- return defaults
-
- def get_config_section(self, name):
- """
- Get a section of a configuration
- """
- if self.config.has_section(name):
- return self.config.items(name)
- return []
-
- def get_environ_vars(self, prefix='VIRTUALENV_'):
- """
- Returns a generator with all environmental vars with prefix VIRTUALENV
- """
- for key, val in os.environ.items():
- if key.startswith(prefix):
- yield (key.replace(prefix, '').lower(), val)
-
- def get_default_values(self):
- """
- Overridding to make updating the defaults after instantiation of
- the option parser possible, update_defaults() does the dirty work.
- """
- if not self.process_default_values:
- # Old, pre-Optik 1.5 behaviour.
- return optparse.Values(self.defaults)
-
- defaults = self.update_defaults(self.defaults.copy()) # ours
- for option in self._get_all_options():
- default = defaults.get(option.dest)
- if isinstance(default, basestring):
- opt_str = option.get_opt_string()
- defaults[option.dest] = option.check_value(opt_str, default)
- return optparse.Values(defaults)
-
-
-def main():
- parser = ConfigOptionParser(
- version=virtualenv_version,
- usage="%prog [OPTIONS] DEST_DIR",
- formatter=UpdatingDefaultsHelpFormatter())
-
- parser.add_option(
- '-v', '--verbose',
- action='count',
- dest='verbose',
- default=0,
- help="Increase verbosity")
-
- parser.add_option(
- '-q', '--quiet',
- action='count',
- dest='quiet',
- default=0,
- help='Decrease verbosity')
-
- parser.add_option(
- '-p', '--python',
- dest='python',
- metavar='PYTHON_EXE',
- help='The Python interpreter to use, e.g., --python=python2.5 will use the python2.5 '
- 'interpreter to create the new environment. The default is the interpreter that '
- 'virtualenv was installed with (%s)' % sys.executable)
-
- parser.add_option(
- '--clear',
- dest='clear',
- action='store_true',
- help="Clear out the non-root install and start from scratch")
-
- parser.set_defaults(system_site_packages=False)
- parser.add_option(
- '--no-site-packages',
- dest='system_site_packages',
- action='store_false',
- help="Don't give access to the global site-packages dir to the "
- "virtual environment (default)")
-
- parser.add_option(
- '--system-site-packages',
- dest='system_site_packages',
- action='store_true',
- help="Give access to the global site-packages dir to the "
- "virtual environment")
-
- parser.add_option(
- '--unzip-setuptools',
- dest='unzip_setuptools',
- action='store_true',
- help="Unzip Setuptools or Distribute when installing it")
-
- parser.add_option(
- '--relocatable',
- dest='relocatable',
- action='store_true',
- help='Make an EXISTING virtualenv environment relocatable. '
- 'This fixes up scripts and makes all .pth files relative')
-
- parser.add_option(
- '--distribute', '--use-distribute', # the second option is for legacy reasons here. Hi Kenneth!
- dest='use_distribute',
- action='store_true',
- help='Use Distribute instead of Setuptools. Set environ variable '
- 'VIRTUALENV_DISTRIBUTE to make it the default ')
-
- parser.add_option(
- '--no-setuptools',
- dest='no_setuptools',
- action='store_true',
- help='Do not install distribute/setuptools (or pip) '
- 'in the new virtualenv.')
-
- parser.add_option(
- '--no-pip',
- dest='no_pip',
- action='store_true',
- help='Do not install pip in the new virtualenv.')
-
- parser.add_option(
- '--setuptools',
- dest='use_distribute',
- action='store_false',
- help='Use Setuptools instead of Distribute. Set environ variable '
- 'VIRTUALENV_SETUPTOOLS to make it the default ')
-
- # Set this to True to use distribute by default, even in Python 2.
- parser.set_defaults(use_distribute=False)
-
- default_search_dirs = file_search_dirs()
- parser.add_option(
- '--extra-search-dir',
- dest="search_dirs",
- action="append",
- default=default_search_dirs,
- help="Directory to look for setuptools/distribute/pip distributions in. "
- "You can add any number of additional --extra-search-dir paths.")
-
- parser.add_option(
- '--never-download',
- dest="never_download",
- action="store_true",
- help="Never download anything from the network. Instead, virtualenv will fail "
- "if local distributions of setuptools/distribute/pip are not present.")
-
- parser.add_option(
- '--prompt',
- dest='prompt',
- help='Provides an alternative prompt prefix for this environment')
-
- if 'extend_parser' in globals():
- extend_parser(parser)
-
- options, args = parser.parse_args()
-
- global logger
-
- if 'adjust_options' in globals():
- adjust_options(options, args)
-
- verbosity = options.verbose - options.quiet
- logger = Logger([(Logger.level_for_integer(2 - verbosity), sys.stdout)])
-
- if options.python and not os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'):
- env = os.environ.copy()
- interpreter = resolve_interpreter(options.python)
- if interpreter == sys.executable:
- logger.warn('Already using interpreter %s' % interpreter)
- else:
- logger.notify('Running virtualenv with interpreter %s' % interpreter)
- env['VIRTUALENV_INTERPRETER_RUNNING'] = 'true'
- file = __file__
- if file.endswith('.pyc'):
- file = file[:-1]
- popen = subprocess.Popen([interpreter, file] + sys.argv[1:], env=env)
- raise SystemExit(popen.wait())
-
- # Force --distribute on Python 3, since setuptools is not available.
- if majver > 2:
- options.use_distribute = True
-
- if os.environ.get('PYTHONDONTWRITEBYTECODE') and not options.use_distribute:
- print(
- "The PYTHONDONTWRITEBYTECODE environment variable is "
- "not compatible with setuptools. Either use --distribute "
- "or unset PYTHONDONTWRITEBYTECODE.")
- sys.exit(2)
- if not args:
- print('You must provide a DEST_DIR')
- parser.print_help()
- sys.exit(2)
- if len(args) > 1:
- print('There must be only one argument: DEST_DIR (you gave %s)' % (
- ' '.join(args)))
- parser.print_help()
- sys.exit(2)
-
- home_dir = args[0]
-
- if os.environ.get('WORKING_ENV'):
- logger.fatal('ERROR: you cannot run virtualenv while in a workingenv')
- logger.fatal('Please deactivate your workingenv, then re-run this script')
- sys.exit(3)
-
- if 'PYTHONHOME' in os.environ:
- logger.warn('PYTHONHOME is set. You *must* activate the virtualenv before using it')
- del os.environ['PYTHONHOME']
-
- if options.relocatable:
- make_environment_relocatable(home_dir)
- return
-
- create_environment(home_dir,
- site_packages=options.system_site_packages,
- clear=options.clear,
- unzip_setuptools=options.unzip_setuptools,
- use_distribute=options.use_distribute,
- prompt=options.prompt,
- search_dirs=options.search_dirs,
- never_download=options.never_download,
- no_setuptools=options.no_setuptools,
- no_pip=options.no_pip)
- if 'after_install' in globals():
- after_install(options, home_dir)
-
-def call_subprocess(cmd, show_stdout=True,
- filter_stdout=None, cwd=None,
- raise_on_returncode=True, extra_env=None,
- remove_from_env=None):
- cmd_parts = []
- for part in cmd:
- if len(part) > 45:
- part = part[:20]+"..."+part[-20:]
- if ' ' in part or '\n' in part or '"' in part or "'" in part:
- part = '"%s"' % part.replace('"', '\\"')
- if hasattr(part, 'decode'):
- try:
- part = part.decode(sys.getdefaultencoding())
- except UnicodeDecodeError:
- part = part.decode(sys.getfilesystemencoding())
- cmd_parts.append(part)
- cmd_desc = ' '.join(cmd_parts)
- if show_stdout:
- stdout = None
- else:
- stdout = subprocess.PIPE
- logger.debug("Running command %s" % cmd_desc)
- if extra_env or remove_from_env:
- env = os.environ.copy()
- if extra_env:
- env.update(extra_env)
- if remove_from_env:
- for varname in remove_from_env:
- env.pop(varname, None)
- else:
- env = None
- try:
- proc = subprocess.Popen(
- cmd, stderr=subprocess.STDOUT, stdin=None, stdout=stdout,
- cwd=cwd, env=env)
- except Exception:
- e = sys.exc_info()[1]
- logger.fatal(
- "Error %s while executing command %s" % (e, cmd_desc))
- raise
- all_output = []
- if stdout is not None:
- stdout = proc.stdout
- encoding = sys.getdefaultencoding()
- fs_encoding = sys.getfilesystemencoding()
- while 1:
- line = stdout.readline()
- try:
- line = line.decode(encoding)
- except UnicodeDecodeError:
- line = line.decode(fs_encoding)
- if not line:
- break
- line = line.rstrip()
- all_output.append(line)
- if filter_stdout:
- level = filter_stdout(line)
- if isinstance(level, tuple):
- level, line = level
- logger.log(level, line)
- if not logger.stdout_level_matches(level):
- logger.show_progress()
- else:
- logger.info(line)
- else:
- proc.communicate()
- proc.wait()
- if proc.returncode:
- if raise_on_returncode:
- if all_output:
- logger.notify('Complete output from command %s:' % cmd_desc)
- logger.notify('\n'.join(all_output) + '\n----------------------------------------')
- raise OSError(
- "Command %s failed with error code %s"
- % (cmd_desc, proc.returncode))
- else:
- logger.warn(
- "Command %s had error code %s"
- % (cmd_desc, proc.returncode))
-
-
-def create_environment(home_dir, site_packages=False, clear=False,
- unzip_setuptools=False, use_distribute=False,
- prompt=None, search_dirs=None, never_download=False,
- no_setuptools=False, no_pip=False):
- """
- Creates a new environment in ``home_dir``.
-
- If ``site_packages`` is true, then the global ``site-packages/``
- directory will be on the path.
-
- If ``clear`` is true (default False) then the environment will
- first be cleared.
- """
- home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir)
-
- py_executable = os.path.abspath(install_python(
- home_dir, lib_dir, inc_dir, bin_dir,
- site_packages=site_packages, clear=clear))
-
- install_distutils(home_dir)
-
- if not no_setuptools:
- if use_distribute:
- install_distribute(py_executable, unzip=unzip_setuptools,
- search_dirs=search_dirs, never_download=never_download)
- else:
- install_setuptools(py_executable, unzip=unzip_setuptools,
- search_dirs=search_dirs, never_download=never_download)
-
- if not no_pip:
- install_pip(py_executable, search_dirs=search_dirs, never_download=never_download)
-
- install_activate(home_dir, bin_dir, prompt)
-
-def is_executable_file(fpath):
- return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
-
-def path_locations(home_dir):
- """Return the path locations for the environment (where libraries are,
- where scripts go, etc)"""
- # XXX: We'd use distutils.sysconfig.get_python_inc/lib but its
- # prefix arg is broken: http://bugs.python.org/issue3386
- if is_win:
- # Windows has lots of problems with executables with spaces in
- # the name; this function will remove them (using the ~1
- # format):
- mkdir(home_dir)
- if ' ' in home_dir:
- import ctypes
- GetShortPathName = ctypes.windll.kernel32.GetShortPathNameW
- size = max(len(home_dir)+1, 256)
- buf = ctypes.create_unicode_buffer(size)
- try:
- u = unicode
- except NameError:
- u = str
- ret = GetShortPathName(u(home_dir), buf, size)
- if not ret:
- print('Error: the path "%s" has a space in it' % home_dir)
- print('We could not determine the short pathname for it.')
- print('Exiting.')
- sys.exit(3)
- home_dir = str(buf.value)
- lib_dir = join(home_dir, 'Lib')
- inc_dir = join(home_dir, 'Include')
- bin_dir = join(home_dir, 'Scripts')
- if is_jython:
- lib_dir = join(home_dir, 'Lib')
- inc_dir = join(home_dir, 'Include')
- bin_dir = join(home_dir, 'bin')
- elif is_pypy:
- lib_dir = home_dir
- inc_dir = join(home_dir, 'include')
- bin_dir = join(home_dir, 'bin')
- elif not is_win:
- lib_dir = join(home_dir, 'lib', py_version)
- multiarch_exec = '/usr/bin/multiarch-platform'
- if is_executable_file(multiarch_exec):
- # In Mageia (2) and Mandriva distros the include dir must be like:
- # virtualenv/include/multiarch-x86_64-linux/python2.7
- # instead of being virtualenv/include/python2.7
- p = subprocess.Popen(multiarch_exec, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- stdout, stderr = p.communicate()
- # stdout.strip is needed to remove newline character
- inc_dir = join(home_dir, 'include', stdout.strip(), py_version + abiflags)
- else:
- inc_dir = join(home_dir, 'include', py_version + abiflags)
- bin_dir = join(home_dir, 'bin')
- return home_dir, lib_dir, inc_dir, bin_dir
-
-
-def change_prefix(filename, dst_prefix):
- prefixes = [sys.prefix]
-
- if is_darwin:
- prefixes.extend((
- os.path.join("/Library/Python", sys.version[:3], "site-packages"),
- os.path.join(sys.prefix, "Extras", "lib", "python"),
- os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"),
- # Python 2.6 no-frameworks
- os.path.join("~", ".local", "lib","python", sys.version[:3], "site-packages"),
- # System Python 2.7 on OSX Mountain Lion
- os.path.join("~", "Library", "Python", sys.version[:3], "lib", "python", "site-packages")))
-
- if hasattr(sys, 'real_prefix'):
- prefixes.append(sys.real_prefix)
- if hasattr(sys, 'base_prefix'):
- prefixes.append(sys.base_prefix)
- prefixes = list(map(os.path.expanduser, prefixes))
- prefixes = list(map(os.path.abspath, prefixes))
- # Check longer prefixes first so we don't split in the middle of a filename
- prefixes = sorted(prefixes, key=len, reverse=True)
- filename = os.path.abspath(filename)
- for src_prefix in prefixes:
- if filename.startswith(src_prefix):
- _, relpath = filename.split(src_prefix, 1)
- if src_prefix != os.sep: # sys.prefix == "/"
- assert relpath[0] == os.sep
- relpath = relpath[1:]
- return join(dst_prefix, relpath)
- assert False, "Filename %s does not start with any of these prefixes: %s" % \
- (filename, prefixes)
-
-def copy_required_modules(dst_prefix):
- import imp
- # If we are running under -p, we need to remove the current
- # directory from sys.path temporarily here, so that we
- # definitely get the modules from the site directory of
- # the interpreter we are running under, not the one
- # virtualenv.py is installed under (which might lead to py2/py3
- # incompatibility issues)
- _prev_sys_path = sys.path
- if os.environ.get('VIRTUALENV_INTERPRETER_RUNNING'):
- sys.path = sys.path[1:]
- try:
- for modname in REQUIRED_MODULES:
- if modname in sys.builtin_module_names:
- logger.info("Ignoring built-in bootstrap module: %s" % modname)
- continue
- try:
- f, filename, _ = imp.find_module(modname)
- except ImportError:
- logger.info("Cannot import bootstrap module: %s" % modname)
- else:
- if f is not None:
- f.close()
- # special-case custom readline.so on OS X, but not for pypy:
- if modname == 'readline' and sys.platform == 'darwin' and not (
- is_pypy or filename.endswith(join('lib-dynload', 'readline.so'))):
- dst_filename = join(dst_prefix, 'lib', 'python%s' % sys.version[:3], 'readline.so')
- else:
- dst_filename = change_prefix(filename, dst_prefix)
- copyfile(filename, dst_filename)
- if filename.endswith('.pyc'):
- pyfile = filename[:-1]
- if os.path.exists(pyfile):
- copyfile(pyfile, dst_filename[:-1])
- finally:
- sys.path = _prev_sys_path
-
-
-def subst_path(prefix_path, prefix, home_dir):
- prefix_path = os.path.normpath(prefix_path)
- prefix = os.path.normpath(prefix)
- home_dir = os.path.normpath(home_dir)
- if not prefix_path.startswith(prefix):
- logger.warn('Path not in prefix %r %r', prefix_path, prefix)
- return
- return prefix_path.replace(prefix, home_dir, 1)
-
-
-def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear):
- """Install just the base environment, no distutils patches etc"""
- if sys.executable.startswith(bin_dir):
- print('Please use the *system* python to run this script')
- return
-
- if clear:
- rmtree(lib_dir)
- ## FIXME: why not delete it?
- ## Maybe it should delete everything with #!/path/to/venv/python in it
- logger.notify('Not deleting %s', bin_dir)
-
- if hasattr(sys, 'real_prefix'):
- logger.notify('Using real prefix %r' % sys.real_prefix)
- prefix = sys.real_prefix
- elif hasattr(sys, 'base_prefix'):
- logger.notify('Using base prefix %r' % sys.base_prefix)
- prefix = sys.base_prefix
- else:
- prefix = sys.prefix
- mkdir(lib_dir)
- fix_lib64(lib_dir)
- stdlib_dirs = [os.path.dirname(os.__file__)]
- if is_win:
- stdlib_dirs.append(join(os.path.dirname(stdlib_dirs[0]), 'DLLs'))
- elif is_darwin:
- stdlib_dirs.append(join(stdlib_dirs[0], 'site-packages'))
- if hasattr(os, 'symlink'):
- logger.info('Symlinking Python bootstrap modules')
- else:
- logger.info('Copying Python bootstrap modules')
- logger.indent += 2
- try:
- # copy required files...
- for stdlib_dir in stdlib_dirs:
- if not os.path.isdir(stdlib_dir):
- continue
- for fn in os.listdir(stdlib_dir):
- bn = os.path.splitext(fn)[0]
- if fn != 'site-packages' and bn in REQUIRED_FILES:
- copyfile(join(stdlib_dir, fn), join(lib_dir, fn))
- # ...and modules
- copy_required_modules(home_dir)
- finally:
- logger.indent -= 2
- mkdir(join(lib_dir, 'site-packages'))
- import site
- site_filename = site.__file__
- if site_filename.endswith('.pyc'):
- site_filename = site_filename[:-1]
- elif site_filename.endswith('$py.class'):
- site_filename = site_filename.replace('$py.class', '.py')
- site_filename_dst = change_prefix(site_filename, home_dir)
- site_dir = os.path.dirname(site_filename_dst)
- writefile(site_filename_dst, SITE_PY)
- writefile(join(site_dir, 'orig-prefix.txt'), prefix)
- site_packages_filename = join(site_dir, 'no-global-site-packages.txt')
- if not site_packages:
- writefile(site_packages_filename, '')
-
- if is_pypy or is_win:
- stdinc_dir = join(prefix, 'include')
- else:
- stdinc_dir = join(prefix, 'include', py_version + abiflags)
- if os.path.exists(stdinc_dir):
- copyfile(stdinc_dir, inc_dir)
- else:
- logger.debug('No include dir %s' % stdinc_dir)
-
- platinc_dir = distutils.sysconfig.get_python_inc(plat_specific=1)
- if platinc_dir != stdinc_dir:
- platinc_dest = distutils.sysconfig.get_python_inc(
- plat_specific=1, prefix=home_dir)
- if platinc_dir == platinc_dest:
- # Do platinc_dest manually due to a CPython bug;
- # not http://bugs.python.org/issue3386 but a close cousin
- platinc_dest = subst_path(platinc_dir, prefix, home_dir)
- if platinc_dest:
- # PyPy's stdinc_dir and prefix are relative to the original binary
- # (traversing virtualenvs), whereas the platinc_dir is relative to
- # the inner virtualenv and ignores the prefix argument.
- # This seems more evolved than designed.
- copyfile(platinc_dir, platinc_dest)
-
- # pypy never uses exec_prefix, just ignore it
- if sys.exec_prefix != prefix and not is_pypy:
- if is_win:
- exec_dir = join(sys.exec_prefix, 'lib')
- elif is_jython:
- exec_dir = join(sys.exec_prefix, 'Lib')
- else:
- exec_dir = join(sys.exec_prefix, 'lib', py_version)
- for fn in os.listdir(exec_dir):
- copyfile(join(exec_dir, fn), join(lib_dir, fn))
-
- if is_jython:
- # Jython has either jython-dev.jar and javalib/ dir, or just
- # jython.jar
- for name in 'jython-dev.jar', 'javalib', 'jython.jar':
- src = join(prefix, name)
- if os.path.exists(src):
- copyfile(src, join(home_dir, name))
- # XXX: registry should always exist after Jython 2.5rc1
- src = join(prefix, 'registry')
- if os.path.exists(src):
- copyfile(src, join(home_dir, 'registry'), symlink=False)
- copyfile(join(prefix, 'cachedir'), join(home_dir, 'cachedir'),
- symlink=False)
-
- mkdir(bin_dir)
- py_executable = join(bin_dir, os.path.basename(sys.executable))
- if 'Python.framework' in prefix:
- # OS X framework builds cause validation to break
- # https://github.com/pypa/virtualenv/issues/322
- if os.environ.get('__PYVENV_LAUNCHER__'):
- os.unsetenv('__PYVENV_LAUNCHER__')
- if re.search(r'/Python(?:-32|-64)*$', py_executable):
- # The name of the python executable is not quite what
- # we want, rename it.
- py_executable = os.path.join(
- os.path.dirname(py_executable), 'python')
-
- logger.notify('New %s executable in %s', expected_exe, py_executable)
- pcbuild_dir = os.path.dirname(sys.executable)
- pyd_pth = os.path.join(lib_dir, 'site-packages', 'virtualenv_builddir_pyd.pth')
- if is_win and os.path.exists(os.path.join(pcbuild_dir, 'build.bat')):
- logger.notify('Detected python running from build directory %s', pcbuild_dir)
- logger.notify('Writing .pth file linking to build directory for *.pyd files')
- writefile(pyd_pth, pcbuild_dir)
- else:
- pcbuild_dir = None
- if os.path.exists(pyd_pth):
- logger.info('Deleting %s (not Windows env or not build directory python)' % pyd_pth)
- os.unlink(pyd_pth)
-
- if sys.executable != py_executable:
- ## FIXME: could I just hard link?
- executable = sys.executable
- shutil.copyfile(executable, py_executable)
- make_exe(py_executable)
- if is_win or is_cygwin:
- pythonw = os.path.join(os.path.dirname(sys.executable), 'pythonw.exe')
- if os.path.exists(pythonw):
- logger.info('Also created pythonw.exe')
- shutil.copyfile(pythonw, os.path.join(os.path.dirname(py_executable), 'pythonw.exe'))
- python_d = os.path.join(os.path.dirname(sys.executable), 'python_d.exe')
- python_d_dest = os.path.join(os.path.dirname(py_executable), 'python_d.exe')
- if os.path.exists(python_d):
- logger.info('Also created python_d.exe')
- shutil.copyfile(python_d, python_d_dest)
- elif os.path.exists(python_d_dest):
- logger.info('Removed python_d.exe as it is no longer at the source')
- os.unlink(python_d_dest)
- # we need to copy the DLL to enforce that windows will load the correct one.
- # may not exist if we are cygwin.
- py_executable_dll = 'python%s%s.dll' % (
- sys.version_info[0], sys.version_info[1])
- py_executable_dll_d = 'python%s%s_d.dll' % (
- sys.version_info[0], sys.version_info[1])
- pythondll = os.path.join(os.path.dirname(sys.executable), py_executable_dll)
- pythondll_d = os.path.join(os.path.dirname(sys.executable), py_executable_dll_d)
- pythondll_d_dest = os.path.join(os.path.dirname(py_executable), py_executable_dll_d)
- if os.path.exists(pythondll):
- logger.info('Also created %s' % py_executable_dll)
- shutil.copyfile(pythondll, os.path.join(os.path.dirname(py_executable), py_executable_dll))
- if os.path.exists(pythondll_d):
- logger.info('Also created %s' % py_executable_dll_d)
- shutil.copyfile(pythondll_d, pythondll_d_dest)
- elif os.path.exists(pythondll_d_dest):
- logger.info('Removed %s as the source does not exist' % pythondll_d_dest)
- os.unlink(pythondll_d_dest)
- if is_pypy:
- # make a symlink python --> pypy-c
- python_executable = os.path.join(os.path.dirname(py_executable), 'python')
- if sys.platform in ('win32', 'cygwin'):
- python_executable += '.exe'
- logger.info('Also created executable %s' % python_executable)
- copyfile(py_executable, python_executable)
-
- if is_win:
- for name in 'libexpat.dll', 'libpypy.dll', 'libpypy-c.dll', 'libeay32.dll', 'ssleay32.dll', 'sqlite.dll':
- src = join(prefix, name)
- if os.path.exists(src):
- copyfile(src, join(bin_dir, name))
-
- if os.path.splitext(os.path.basename(py_executable))[0] != expected_exe:
- secondary_exe = os.path.join(os.path.dirname(py_executable),
- expected_exe)
- py_executable_ext = os.path.splitext(py_executable)[1]
- if py_executable_ext == '.exe':
- # python2.4 gives an extension of '.4' :P
- secondary_exe += py_executable_ext
- if os.path.exists(secondary_exe):
- logger.warn('Not overwriting existing %s script %s (you must use %s)'
- % (expected_exe, secondary_exe, py_executable))
- else:
- logger.notify('Also creating executable in %s' % secondary_exe)
- shutil.copyfile(sys.executable, secondary_exe)
- make_exe(secondary_exe)
-
- if '.framework' in prefix:
- if 'Python.framework' in prefix:
- logger.debug('MacOSX Python framework detected')
- # Make sure we use the 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
- original_python = os.path.join(
- prefix, 'Resources/Python.app/Contents/MacOS/Python')
- if 'EPD' in prefix:
- logger.debug('EPD framework detected')
- original_python = os.path.join(prefix, 'bin/python')
- shutil.copy(original_python, py_executable)
-
- # Copy the framework's dylib into the virtual
- # environment
- virtual_lib = os.path.join(home_dir, '.Python')
-
- if os.path.exists(virtual_lib):
- os.unlink(virtual_lib)
- copyfile(
- os.path.join(prefix, 'Python'),
- virtual_lib)
-
- # And then change the install_name of the copied python executable
- try:
- mach_o_change(py_executable,
- os.path.join(prefix, 'Python'),
- '@executable_path/../.Python')
- except:
- e = sys.exc_info()[1]
- logger.warn("Could not call mach_o_change: %s. "
- "Trying to call install_name_tool instead." % e)
- try:
- call_subprocess(
- ["install_name_tool", "-change",
- os.path.join(prefix, 'Python'),
- '@executable_path/../.Python',
- py_executable])
- except:
- logger.fatal("Could not call install_name_tool -- you must "
- "have Apple's development tools installed")
- raise
-
- if not is_win:
- # Ensure that 'python', 'pythonX' and 'pythonX.Y' all exist
- py_exe_version_major = 'python%s' % sys.version_info[0]
- py_exe_version_major_minor = 'python%s.%s' % (
- sys.version_info[0], sys.version_info[1])
- py_exe_no_version = 'python'
- required_symlinks = [ py_exe_no_version, py_exe_version_major,
- py_exe_version_major_minor ]
-
- py_executable_base = os.path.basename(py_executable)
-
- if py_executable_base in required_symlinks:
- # Don't try to symlink to yourself.
- required_symlinks.remove(py_executable_base)
-
- for pth in required_symlinks:
- full_pth = join(bin_dir, pth)
- if os.path.exists(full_pth):
- os.unlink(full_pth)
- os.symlink(py_executable_base, full_pth)
-
- if is_win and ' ' in py_executable:
- # There's a bug with subprocess on Windows when using a first
- # argument that has a space in it. Instead we have to quote
- # the value:
- py_executable = '"%s"' % py_executable
- # NOTE: keep this check as one line, cmd.exe doesn't cope with line breaks
- cmd = [py_executable, '-c', 'import sys;out=sys.stdout;'
- 'getattr(out, "buffer", out).write(sys.prefix.encode("utf-8"))']
- logger.info('Testing executable with %s %s "%s"' % tuple(cmd))
- try:
- proc = subprocess.Popen(cmd,
- stdout=subprocess.PIPE)
- proc_stdout, proc_stderr = proc.communicate()
- except OSError:
- e = sys.exc_info()[1]
- if e.errno == errno.EACCES:
- logger.fatal('ERROR: The executable %s could not be run: %s' % (py_executable, e))
- sys.exit(100)
- else:
- raise e
-
- proc_stdout = proc_stdout.strip().decode("utf-8")
- proc_stdout = os.path.normcase(os.path.abspath(proc_stdout))
- norm_home_dir = os.path.normcase(os.path.abspath(home_dir))
- if hasattr(norm_home_dir, 'decode'):
- norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding())
- if proc_stdout != norm_home_dir:
- logger.fatal(
- 'ERROR: The executable %s is not functioning' % py_executable)
- logger.fatal(
- 'ERROR: It thinks sys.prefix is %r (should be %r)'
- % (proc_stdout, norm_home_dir))
- logger.fatal(
- 'ERROR: virtualenv is not compatible with this system or executable')
- if is_win:
- logger.fatal(
- 'Note: some Windows users have reported this error when they '
- 'installed Python for "Only this user" or have multiple '
- 'versions of Python installed. Copying the appropriate '
- 'PythonXX.dll to the virtualenv Scripts/ directory may fix '
- 'this problem.')
- sys.exit(100)
- else:
- logger.info('Got sys.prefix result: %r' % proc_stdout)
-
- pydistutils = os.path.expanduser('~/.pydistutils.cfg')
- if os.path.exists(pydistutils):
- logger.notify('Please make sure you remove any previous custom paths from '
- 'your %s file.' % pydistutils)
- ## FIXME: really this should be calculated earlier
-
- fix_local_scheme(home_dir)
-
- if site_packages:
- if os.path.exists(site_packages_filename):
- logger.info('Deleting %s' % site_packages_filename)
- os.unlink(site_packages_filename)
-
- return py_executable
-
-
-def install_activate(home_dir, bin_dir, prompt=None):
- home_dir = os.path.abspath(home_dir)
- if is_win or is_jython and os._name == 'nt':
- files = {
- 'activate.bat': ACTIVATE_BAT,
- 'deactivate.bat': DEACTIVATE_BAT,
- 'activate.ps1': ACTIVATE_PS,
- }
-
- # MSYS needs paths of the form /c/path/to/file
- drive, tail = os.path.splitdrive(home_dir.replace(os.sep, '/'))
- home_dir_msys = (drive and "/%s%s" or "%s%s") % (drive[:1], tail)
-
- # Run-time conditional enables (basic) Cygwin compatibility
- home_dir_sh = ("""$(if [ "$OSTYPE" "==" "cygwin" ]; then cygpath -u '%s'; else echo '%s'; fi;)""" %
- (home_dir, home_dir_msys))
- files['activate'] = ACTIVATE_SH.replace('__VIRTUAL_ENV__', home_dir_sh)
-
- else:
- files = {'activate': ACTIVATE_SH}
-
- # suppling activate.fish in addition to, not instead of, the
- # bash script support.
- files['activate.fish'] = ACTIVATE_FISH
-
- # same for csh/tcsh support...
- files['activate.csh'] = ACTIVATE_CSH
-
- files['activate_this.py'] = ACTIVATE_THIS
- if hasattr(home_dir, 'decode'):
- home_dir = home_dir.decode(sys.getfilesystemencoding())
- vname = os.path.basename(home_dir)
- for name, content in files.items():
- content = content.replace('__VIRTUAL_PROMPT__', prompt or '')
- content = content.replace('__VIRTUAL_WINPROMPT__', prompt or '(%s)' % vname)
- content = content.replace('__VIRTUAL_ENV__', home_dir)
- content = content.replace('__VIRTUAL_NAME__', vname)
- content = content.replace('__BIN_NAME__', os.path.basename(bin_dir))
- writefile(os.path.join(bin_dir, name), content)
-
-def install_distutils(home_dir):
- distutils_path = change_prefix(distutils.__path__[0], home_dir)
- mkdir(distutils_path)
- ## FIXME: maybe this prefix setting should only be put in place if
- ## there's a local distutils.cfg with a prefix setting?
- home_dir = os.path.abspath(home_dir)
- ## FIXME: this is breaking things, removing for now:
- #distutils_cfg = DISTUTILS_CFG + "\n[install]\nprefix=%s\n" % home_dir
- writefile(os.path.join(distutils_path, '__init__.py'), DISTUTILS_INIT)
- writefile(os.path.join(distutils_path, 'distutils.cfg'), DISTUTILS_CFG, overwrite=False)
-
-def fix_local_scheme(home_dir):
- """
- Platforms that use the "posix_local" install scheme (like Ubuntu with
- Python 2.7) need to be given an additional "local" location, sigh.
- """
- try:
- import sysconfig
- except ImportError:
- pass
- else:
- if sysconfig._get_default_scheme() == 'posix_local':
- local_path = os.path.join(home_dir, 'local')
- if not os.path.exists(local_path):
- os.mkdir(local_path)
- for subdir_name in os.listdir(home_dir):
- if subdir_name == 'local':
- continue
- os.symlink(os.path.abspath(os.path.join(home_dir, subdir_name)), \
- os.path.join(local_path, subdir_name))
-
-def fix_lib64(lib_dir):
- """
- Some platforms (particularly Gentoo on x64) put things in lib64/pythonX.Y
- instead of lib/pythonX.Y. If this is such a platform we'll just create a
- symlink so lib64 points to lib
- """
- if [p for p in distutils.sysconfig.get_config_vars().values()
- if isinstance(p, basestring) and 'lib64' in p]:
- logger.debug('This system uses lib64; symlinking lib64 to lib')
- assert os.path.basename(lib_dir) == 'python%s' % sys.version[:3], (
- "Unexpected python lib dir: %r" % lib_dir)
- lib_parent = os.path.dirname(lib_dir)
- top_level = os.path.dirname(lib_parent)
- lib_dir = os.path.join(top_level, 'lib')
- lib64_link = os.path.join(top_level, 'lib64')
- assert os.path.basename(lib_parent) == 'lib', (
- "Unexpected parent dir: %r" % lib_parent)
- if os.path.lexists(lib64_link):
- return
- os.symlink('lib', lib64_link)
-
-def resolve_interpreter(exe):
- """
- If the executable given isn't an absolute path, search $PATH for the interpreter
- """
- if os.path.abspath(exe) != exe:
- paths = os.environ.get('PATH', '').split(os.pathsep)
- for path in paths:
- if os.path.exists(os.path.join(path, exe)):
- exe = os.path.join(path, exe)
- break
- if not os.path.exists(exe):
- logger.fatal('The executable %s (from --python=%s) does not exist' % (exe, exe))
- raise SystemExit(3)
- if not is_executable(exe):
- logger.fatal('The executable %s (from --python=%s) is not executable' % (exe, exe))
- raise SystemExit(3)
- return exe
-
-def is_executable(exe):
- """Checks a file is executable"""
- return os.access(exe, os.X_OK)
-
-############################################################
-## Relocating the environment:
-
-def make_environment_relocatable(home_dir):
- """
- Makes the already-existing environment use relative paths, and takes out
- the #!-based environment selection in scripts.
- """
- home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir)
- activate_this = os.path.join(bin_dir, 'activate_this.py')
- if not os.path.exists(activate_this):
- logger.fatal(
- 'The environment doesn\'t have a file %s -- please re-run virtualenv '
- 'on this environment to update it' % activate_this)
- fixup_scripts(home_dir)
- fixup_pth_and_egg_link(home_dir)
- ## FIXME: need to fix up distutils.cfg
-
-OK_ABS_SCRIPTS = ['python', 'python%s' % sys.version[:3],
- 'activate', 'activate.bat', 'activate_this.py']
-
-def fixup_scripts(home_dir):
- # This is what we expect at the top of scripts:
- shebang = '#!%s/bin/python' % os.path.normcase(os.path.abspath(home_dir))
- # This is what we'll put:
- new_shebang = '#!/usr/bin/env python%s' % sys.version[:3]
- if is_win:
- bin_suffix = 'Scripts'
- else:
- bin_suffix = 'bin'
- bin_dir = os.path.join(home_dir, bin_suffix)
- home_dir, lib_dir, inc_dir, bin_dir = path_locations(home_dir)
- for filename in os.listdir(bin_dir):
- filename = os.path.join(bin_dir, filename)
- if not os.path.isfile(filename):
- # ignore subdirs, e.g. .svn ones.
- continue
- f = open(filename, 'rb')
- try:
- try:
- lines = f.read().decode('utf-8').splitlines()
- except UnicodeDecodeError:
- # This is probably a binary program instead
- # of a script, so just ignore it.
- continue
- finally:
- f.close()
- if not lines:
- logger.warn('Script %s is an empty file' % filename)
- continue
- if not lines[0].strip().startswith(shebang):
- if os.path.basename(filename) in OK_ABS_SCRIPTS:
- logger.debug('Cannot make script %s relative' % filename)
- elif lines[0].strip() == new_shebang:
- logger.info('Script %s has already been made relative' % filename)
- else:
- logger.warn('Script %s cannot be made relative (it\'s not a normal script that starts with %s)'
- % (filename, shebang))
- continue
- logger.notify('Making script %s relative' % filename)
- script = relative_script([new_shebang] + lines[1:])
- f = open(filename, 'wb')
- f.write('\n'.join(script).encode('utf-8'))
- f.close()
-
-def relative_script(lines):
- "Return a script that'll work in a relocatable environment."
- activate = "import os; activate_this=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'activate_this.py'); execfile(activate_this, dict(__file__=activate_this)); del os, activate_this"
- # Find the last future statement in the script. If we insert the activation
- # line before a future statement, Python will raise a SyntaxError.
- activate_at = None
- for idx, line in reversed(list(enumerate(lines))):
- if line.split()[:3] == ['from', '__future__', 'import']:
- activate_at = idx + 1
- break
- if activate_at is None:
- # Activate after the shebang.
- activate_at = 1
- return lines[:activate_at] + ['', activate, ''] + lines[activate_at:]
-
-def fixup_pth_and_egg_link(home_dir, sys_path=None):
- """Makes .pth and .egg-link files use relative paths"""
- home_dir = os.path.normcase(os.path.abspath(home_dir))
- if sys_path is None:
- sys_path = sys.path
- for path in sys_path:
- if not path:
- path = '.'
- if not os.path.isdir(path):
- continue
- path = os.path.normcase(os.path.abspath(path))
- if not path.startswith(home_dir):
- logger.debug('Skipping system (non-environment) directory %s' % path)
- continue
- for filename in os.listdir(path):
- filename = os.path.join(path, filename)
- if filename.endswith('.pth'):
- if not os.access(filename, os.W_OK):
- logger.warn('Cannot write .pth file %s, skipping' % filename)
- else:
- fixup_pth_file(filename)
- if filename.endswith('.egg-link'):
- if not os.access(filename, os.W_OK):
- logger.warn('Cannot write .egg-link file %s, skipping' % filename)
- else:
- fixup_egg_link(filename)
-
-def fixup_pth_file(filename):
- lines = []
- prev_lines = []
- f = open(filename)
- prev_lines = f.readlines()
- f.close()
- for line in prev_lines:
- line = line.strip()
- if (not line or line.startswith('#') or line.startswith('import ')
- or os.path.abspath(line) != line):
- lines.append(line)
- else:
- new_value = make_relative_path(filename, line)
- if line != new_value:
- logger.debug('Rewriting path %s as %s (in %s)' % (line, new_value, filename))
- lines.append(new_value)
- if lines == prev_lines:
- logger.info('No changes to .pth file %s' % filename)
- return
- logger.notify('Making paths in .pth file %s relative' % filename)
- f = open(filename, 'w')
- f.write('\n'.join(lines) + '\n')
- f.close()
-
-def fixup_egg_link(filename):
- f = open(filename)
- link = f.readline().strip()
- f.close()
- if os.path.abspath(link) != link:
- logger.debug('Link in %s already relative' % filename)
- return
- new_link = make_relative_path(filename, link)
- logger.notify('Rewriting link %s in %s as %s' % (link, filename, new_link))
- f = open(filename, 'w')
- f.write(new_link)
- f.close()
-
-def make_relative_path(source, dest, dest_is_directory=True):
- """
- Make a filename relative, where the filename is dest, and it is
- being referred to from the filename source.
-
- >>> make_relative_path('/usr/share/something/a-file.pth',
- ... '/usr/share/another-place/src/Directory')
- '../another-place/src/Directory'
- >>> make_relative_path('/usr/share/something/a-file.pth',
- ... '/home/user/src/Directory')
- '../../../home/user/src/Directory'
- >>> make_relative_path('/usr/share/a-file.pth', '/usr/share/')
- './'
- """
- source = os.path.dirname(source)
- if not dest_is_directory:
- dest_filename = os.path.basename(dest)
- dest = os.path.dirname(dest)
- dest = os.path.normpath(os.path.abspath(dest))
- source = os.path.normpath(os.path.abspath(source))
- dest_parts = dest.strip(os.path.sep).split(os.path.sep)
- source_parts = source.strip(os.path.sep).split(os.path.sep)
- while dest_parts and source_parts and dest_parts[0] == source_parts[0]:
- dest_parts.pop(0)
- source_parts.pop(0)
- full_parts = ['..']*len(source_parts) + dest_parts
- if not dest_is_directory:
- full_parts.append(dest_filename)
- if not full_parts:
- # Special case for the current directory (otherwise it'd be '')
- return './'
- return os.path.sep.join(full_parts)
-
-
-
-############################################################
-## Bootstrap script creation:
-
-def create_bootstrap_script(extra_text, python_version=''):
- """
- Creates a bootstrap script, which is like this script but with
- extend_parser, adjust_options, and after_install hooks.
-
- This returns a string that (written to disk of course) can be used
- as a bootstrap script with your own customizations. The script
- will be the standard virtualenv.py script, with your extra text
- added (your extra text should be Python code).
-
- If you include these functions, they will be called:
-
- ``extend_parser(optparse_parser)``:
- You can add or remove options from the parser here.
-
- ``adjust_options(options, args)``:
- You can change options here, or change the args (if you accept
- different kinds of arguments, be sure you modify ``args`` so it is
- only ``[DEST_DIR]``).
-
- ``after_install(options, home_dir)``:
-
- After everything is installed, this function is called. This
- is probably the function you are most likely to use. An
- example would be::
-
- def after_install(options, home_dir):
- subprocess.call([join(home_dir, 'bin', 'easy_install'),
- 'MyPackage'])
- subprocess.call([join(home_dir, 'bin', 'my-package-script'),
- 'setup', home_dir])
-
- This example immediately installs a package, and runs a setup
- script from that package.
-
- If you provide something like ``python_version='2.5'`` then the
- script will start with ``#!/usr/bin/env python2.5`` instead of
- ``#!/usr/bin/env python``. You can use this when the script must
- be run with a particular Python version.
- """
- filename = __file__
- if filename.endswith('.pyc'):
- filename = filename[:-1]
- f = codecs.open(filename, 'r', encoding='utf-8')
- content = f.read()
- f.close()
- py_exe = 'python%s' % python_version
- content = (('#!/usr/bin/env %s\n' % py_exe)
- + '## WARNING: This file is generated\n'
- + content)
- return content.replace('##EXT' 'END##', extra_text)
-
-##EXTEND##
-
-def convert(s):
- b = base64.b64decode(s.encode('ascii'))
- return zlib.decompress(b).decode('utf-8')
-
-##file site.py
-SITE_PY = convert("""
-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
-UM3ckC/Q8aP8VLy+mjYY5MxOtAdgjULwf2RtvCc=
-""")
-
-##file ez_setup.py
-EZ_SETUP_PY = convert("""
-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
-qL7+C1AkCcw=
-""")
-
-##file distribute_from_egg.py
-DISTRIBUTE_FROM_EGG_PY = convert("""
-eJw9j8tqAzEMRfcG/4MgmxQyptkGusonZBmGoGTUGYFfWPKE6dfXTkM3gqt7rh47OKP3NMF3SQFW
-LlrRU1zhybpAxoKBlIqcrNnBdRjQP3GTocYfzmNrrCPQPN9iwzpxSQfQhWBi0cL3qtRtYIG/4Mv0
-KApY5hooqrOGQ05FQTaxptF9Fnx16Rq0XofjaE1XGXVxHIWK7j8P8EY/rHndLqQ1a0pe3COFgHFy
-hLLdWkDbi/DeEpCjNb3u/zccT2Ob8gtnwVyI
-""")
-
-##file distribute_setup.py
-DISTRIBUTE_SETUP_PY = convert("""
-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
-""")
-
-##file activate.sh
-ACTIVATE_SH = convert("""
-eJytVVFvokAQfudXTLEPtTlLeo9tvMSmJpq02hSvl7u2wRUG2QR2DSxSe7n/frOACEVNLlceRHa+
-nfl25pvZDswCnoDPQ4QoTRQsENIEPci4CsBMZBq7CAsuLOYqvmYKTTj3YxnBgiXBudGBjUzBZUJI
-BXEqgCvweIyuCjeG4eF2F5x14bcB9KQiQQWrjSddI1/oQIx6SYYeoFjzWIoIhYI1izlbhJjkKO7D
-M/QEmKfO9O7WeRo/zr4P7pyHwWxkwitcgwpQ5Ej96OX+PmiFwLeVjFUOrNYKaq1Nud3nR2n8nI2m
-k9H0friPTGVsUdptaxGrTEfpNVFEskxpXtUkkCkl1UNF9cgLBkx48J4EXyALuBtAwNYIjF5kcmUU
-abMKmMq1ULoiRbgsDEkTSsKSGFCJ6Z8vY/2xYiSacmtyAfCDdCNTVZoVF8vSTQOoEwSnOrngBkws
-MYGMBMg8/bMBLSYKS7pYEXP0PqT+ZmBT0Xuy+Pplj5yn4aM9nk72JD8/Wi+Gr98sD9eWSMOwkapD
-BbUv91XSvmyVkICt2tmXR4tWmrcUCsjWOpw87YidEC8i0gdTSOFhouJUNxR+4NYBG0MftoCTD9F7
-2rTtxG3oPwY1b2HncYwhrlmj6Wq924xtGDWqfdNxap+OYxplEurnMVo9RWks+rH8qKEtx7kZT5zJ
-4H7oOFclrN6uFe+d+nW2aIUsSgs/42EIPuOhXq+jEo3S6tX6w2ilNkDnIpHCWdEQhFgwj9pkk7FN
-l/y5eQvRSIQ5+TrL05lewxWpt/Lbhes5cJF3mLET1MGhcKCF+40tNWnUulxrpojwDo2sObdje3Bz
-N3QeHqf3D7OjEXMVV8LN3ZlvuzoWHqiUcNKHtwNd0IbvPGKYYM31nPKCgkUILw3KL+Y8l7aO1ArS
-Ad37nIU0fCj5NE5gQCuC5sOSu+UdI2NeXg/lFkQIlFpdWVaWZRfvqGiirC9o6liJ9FXGYrSY9mI1
-D/Ncozgn13vJvsznr7DnkJWXsyMH7e42ljdJ+aqNDF1bFnKWFLdj31xtaJYK6EXFgqmV/ymD/ROG
-+n8O9H8f5vsGOWXsL1+1k3g=
-""")
-
-##file activate.fish
-ACTIVATE_FISH = convert("""
-eJyVVWFv2jAQ/c6vuBoqQVWC9nVSNVGVCaS2VC2rNLWVZZILWAs2s52wVvvxsyEJDrjbmgpK7PP5
-3bt3d22YLbmGlGcIq1wbmCPkGhPYcLMEEsGciwGLDS+YwSjlekngLFVyBe73GXSXxqw/DwbuTS8x
-yyKpFr1WG15lDjETQhpQuQBuIOEKY5O9tlppLqxHKSDByjVAPwEy+mXtCq5MzjIUBTCRgEKTKwFG
-gpBqxTLYXgN2myspVigMaYF92tZSowGZJf4mFExxNs9Qb614CgZtmH0BpEOn11f0cXI/+za8pnfD
-2ZjA1sg9zlV/8QvcMhxbNu0QwgYokn/d+n02nt6Opzcjcnx1vXcIoN74O4ymWQXmHURfJw9jenc/
-vbmb0enj6P5+cuVhqlKm3S0u2XRtRbA2QQAhV7VhBF0rsgUX9Ur1rBUXJgVSy8O751k8mzY5OrKH
-RW3eaQhYGTr8hrXO59ALhxQ83mCsDLAid3T72CCSdJhaFE+fXgicXAARUiR2WeVO37gH3oYHzFKo
-9k7CaPZ1UeNwH1tWuXA4uFKYYcEa8vaKqXl7q1UpygMPhFLvlVKyNzsSM3S2km7UBOl4xweUXk5u
-6e3wZmQ9leY1XE/Ili670tr9g/5POBBpGIJXCCF79L1siarl/dbESa8mD8PL61GpzqpzuMS7tqeB
-1YkALrRBloBMbR9yLcVx7frQAgUqR7NZIuzkEu110gbNit1enNs82Rx5utq7Z3prU78HFRgulqNC
-OTwbqJa9vkJFclQgZSjbKeBgSsUtCtt9D8OwAbIVJuewQdfvQRaoFE9wd1TmCuRG7OgJ1bVXGHc7
-z5WDL/WW36v2oi37CyVBak61+yPBA9C1qqGxzKQqZ0oPuocU9hpud0PIp8sDHkXR1HKkNlzjuUWA
-a0enFUyzOWZA4yXGP+ZMI3Tdt2OuqU/SO4q64526cPE0A7ZyW2PMbWZiZ5HamIZ2RcCKLXhcDl2b
-vXL+eccQoRzem80mekPDEiyiWK4GWqZmwxQOmPM0eIfgp1P9cqrBsewR2p/DPMtt+pfcYM+Ls2uh
-hALufTAdmGl8B1H3VPd2af8fQAc4PgqjlIBL9cGQqNpXaAwe3LrtVn8AkZTUxg==
-""")
-
-##file activate.csh
-ACTIVATE_CSH = convert("""
-eJx9VG1P2zAQ/u5fcYQKNgTNPtN1WxlIQ4KCUEGaxuQ6yYVYSuzKdhqVX7+zk3bpy5YPUXL3PPfc
-ne98DLNCWshliVDV1kGCUFvMoJGugMjq2qQIiVSxSJ1cCofD1BYRnOVGV0CfZ0N2DD91DalQSjsw
-tQLpIJMGU1euvPe7QeJlkKzgWixlhnAt4aoUVsLnLBiy5NtbJWQ5THX1ZciYKKWwkOFaE04dUm6D
-r/zh7pq/3D7Nnid3/HEy+wFHY/gEJydg0aFaQrBFgz1c5DG1IhTs+UZgsBC2GMFBlaeH+8dZXwcW
-VPvCjXdlAvCfQsE7al0+07XjZvrSCUevR5dnkVeKlFYZmUztG4BdzL2u9KyLVabTU0bdfg7a0hgs
-cSmUg6UwUiQl2iHrcbcVGNvPCiLOe7+cRwG13z9qRGgx2z6DHjfm/Op2yqeT+xvOLzs0PTKHDz2V
-tkckFHoQfQRXoGJAj9el0FyJCmEMhzgMS4sB7KPOE2ExoLcSieYwDvR+cP8cg11gKkVJc2wRcm1g
-QhYFlXiTaTfO2ki0fQoiFM4tLuO4aZrhOzqR4dIPcWx17hphMBY+Srwh7RTyN83XOWkcSPh1Pg/k
-TXX/jbJTbMtUmcxZ+/bbqOsy82suFQg/BhdSOTRhMNBHlUarCpU7JzBhmkKmRejKOQzayQe6MWoa
-n1wqWmuh6LZAaHxcdeqIlVLhIBJdO9/kbl0It2oEXQj+eGjJOuvOIR/YGRqvFhttUB2XTvLXYN2H
-37CBdbW2W7j2r2+VsCn0doVWcFG1/4y1VwBjfwAyoZhD
-""")
-
-##file activate.bat
-ACTIVATE_BAT = convert("""
-eJx9UdEKgjAUfW6wfxjiIH+hEDKUFHSKLCMI7kNOEkIf9P9pTJ3OLJ/03HPPPed4Es9XS9qqwqgT
-PbGKKOdXL4aAFS7A4gvAwgijuiKlqOpGlATS2NeMLE+TjJM9RkQ+SmqAXLrBo1LLIeLdiWlD6jZt
-r7VNubWkndkXaxg5GO3UaOOKS6drO3luDDiO5my3iA0YAKGzPRV1ack8cOdhysI0CYzIPzjSiH5X
-0QcvC8Lfaj0emsVKYF2rhL5L3fCkVjV76kShi59NHwDniAHzkgDgqBcwOgTMx+gDQQqXCw==
-""")
-
-##file deactivate.bat
-DEACTIVATE_BAT = convert("""
-eJxzSE3OyFfIT0vj4ipOLVEI8wwKCXX0iXf1C7Pl4spMU0hJTcvMS01RiPf3cYmHyQYE+fsGhCho
-cCkAAUibEkTEVhWLMlUlLk6QGixStlyaeCyJDPHw9/Pw93VFsQguim4ZXAJoIUw5DhX47XUM8UCx
-EchHtwsohN1bILUgw61c/Vy4AJYPYm4=
-""")
-
-##file activate.ps1
-ACTIVATE_PS = convert("""
-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
-QastYw==
-""")
-
-##file distutils-init.py
-DISTUTILS_INIT = convert("""
-eJytV1uL4zYUfvevOE0ottuMW9q3gVDa3aUMXXbLMlDKMBiNrSTqOJKRlMxkf33PkXyRbGe7Dw2E
-UXTu37lpxLFV2oIyifAncxmOL0xLIfcG+gv80x9VW6maw7o/CANSWWBwFtqeWMPlGY6qPjV8A0bB
-C4eKSTgZ5LRgFeyErMEeOBhbN+Ipgeizhjtnhkn7DdyjuNLPoCS0l/ayQTG0djwZC08cLXozeMss
-aG5EzQ0IScpnWtHSTXuxByV/QCmxE7y+eS0uxWeoheaVVfqSJHiU7Mhhi6gULbOHorshkrEnKxpT
-0n3A8Y8SMpuwZx6aoix3ouFlmW8gHRSkeSJ2g7hU+kiHLDaQw3bmRDaTGfTnty7gPm0FHbIBg9U9
-oh1kZzAFLaue2R6htPCtAda2nGlDSUJ4PZBgCJBGVcwKTAMz/vJiLD+Oin5Z5QlvDPdulC6EsiyE
-NFzb7McNTKJzbJqzphx92VKRFY1idenzmq3K0emRcbWBD0ryqc4NZGmKOOOX9Pz5x+/l27tP797c
-f/z0d+4NruGNai8uAM0bfsYaw8itFk8ny41jsfpyO+BWlpqfhcG4yxLdi/0tQqoT4a8Vby382mt8
-p7XSo7aWGdPBc+b6utaBmCQ7rQKQoWtAuthQCiold2KfJIPTT8xwg9blPumc+YDZC/wYGdAyHpJk
-vUbHbHWAp5No6pK/WhhLEWrFjUwtPEv1Agf8YmnsuXUQYkeZoHm8ogP16gt2uHoxcEMdf2C6pmbw
-hUMsWGhanboh4IzzmsIpWs134jVPqD/c74bZHdY69UKKSn/+KfVhxLgUlToemayLMYQOqfEC61bh
-cbhwaqoGUzIyZRFHPmau5juaWqwRn3mpWmoEA5nhzS5gog/5jbcFQqOZvmBasZtwYlG93k5GEiyw
-buHhMWLjDarEGpMGB2LFs5nIJkhp/nUmZneFaRth++lieJtHepIvKgx6PJqIlD9X2j6pG1i9x3pZ
-5bHuCPFiirGHeO7McvoXkz786GaKVzC9DSpnOxJdc4xm6NSVq7lNEnKdVlnpu9BNYoKX2Iq3wvgh
-gGEUM66kK6j4NiyoneuPLSwaCWDxczgaolEWpiMyDVDb7dNuLAbriL8ig8mmeju31oNvQdpnvEPC
-1vAXbWacGRVrGt/uXN/gU0CDDwgooKRrHfTBb1/s9lYZ8ZqOBU0yLvpuP6+K9hLFsvIjeNhBi0KL
-MlOuWRn3FRwx5oHXjl0YImUx0+gLzjGchrgzca026ETmYJzPD+IpuKzNi8AFn048Thd63OdD86M6
-84zE8yQm0VqXdbbgvub2pKVnS76icBGdeTHHXTKspUmr4NYo/furFLKiMdQzFjHJNcdAnMhltBJK
-0/IKX3DVFqvPJ2dLE7bDBkH0l/PJ29074+F0CsGYOxsb7U3myTUncYfXqnLLfa6sJybX4g+hmcjO
-kMRBfA1JellfRRKJcyRpxdS4rIl6FdmQCWjo/o9Qz7yKffoP4JHjOvABcRn4CZIT2RH4jnxmfpVG
-qgLaAvQBNfuO6X0/Ux02nb4FKx3vgP+XnkX0QW9pLy/NsXgdN24dD3LxO2Nwil7Zlc1dqtP3d7/h
-kzp1/+7hGBuY4pk0XD/0Ao/oTe/XGrfyM773aB7iUhgkpy+dwAMalxMP0DrBcsVw/6p25+/hobP9
-GBknrWExDhLJ1bwt1NcCNblaFbMKCyvmX0PeRaQ=
-""")
-
-##file distutils.cfg
-DISTUTILS_CFG = convert("""
-eJxNj00KwkAMhfc9xYNuxe4Ft57AjYiUtDO1wXSmNJnK3N5pdSEEAu8nH6lxHVlRhtDHMPATA4uH
-xJ4EFmGbvfJiicSHFRzUSISMY6hq3GLCRLnIvSTnEefN0FIjw5tF0Hkk9Q5dRunBsVoyFi24aaLg
-9FDOlL0FPGluf4QjcInLlxd6f6rqkgPu/5nHLg0cXCscXoozRrP51DRT3j9QNl99AP53T2Q=
-""")
-
-##file activate_this.py
-ACTIVATE_THIS = convert("""
-eJyNU01v2zAMvetXEB4K21jmDOstQA4dMGCHbeihlyEIDMWmG62yJEiKE//7kXKdpN2KzYBt8euR
-fKSyLPs8wiEo8wh4wqZTGou4V6Hm0wJa1cSiTkJdr8+GsoTRHuCotBayiWqQEYGtMCgfD1KjGYBe
-5a3p0cRKiAe2NtLADikftnDco0ko/SFEVgEZ8aRC5GLux7i3BpSJ6J1H+i7A2CjiHq9z7JRZuuQq
-siwTIvpxJYCeuWaBpwZdhB+yxy/eWz+ZvVSU8C4E9FFZkyxFsvCT/ZzL8gcz9aXVE14Yyp2M+2W0
-y7n5mp0qN+avKXvbsyyzUqjeWR8hjGE+2iCE1W1tQ82hsCZN9UzlJr+/e/iab8WfqsmPI6pWeUPd
-FrMsd4H/55poeO9n54COhUs+sZNEzNtg/wanpjpuqHJaxs76HtZryI/K3H7KJ/KDIhqcbJ7kI4ar
-XL+sMgXnX0D+Te2Iy5xdP8yueSlQB/x/ED2BTAtyE3K4SYUN6AMNfbO63f4lBW3bUJPbTL+mjSxS
-PyRfJkZRgj+VbFv+EzHFi5pKwUEepa4JslMnwkowSRCXI+m5XvEOvtuBrxHdhLalG0JofYBok6qj
-YdN2dEngUlbC4PG60M1WEN0piu7Nq7on0mgyyUw3iV1etLo6r/81biWdQ9MWHFaePWZYaq+nmp+t
-s3az+sj7eA0jfgPfeoN1
-""")
-
-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
-maxint = majver == 3 and getattr(sys, 'maxsize') or getattr(sys, 'maxint')
-
-
-class fileview(object):
- """
- A proxy for file-like objects that exposes a given view of a file.
- Modified from macholib.
- """
-
- def __init__(self, fileobj, start=0, size=maxint):
- if isinstance(fileobj, fileview):
- self._fileobj = fileobj._fileobj
- else:
- self._fileobj = fileobj
- self._start = start
- self._end = start + size
- self._pos = 0
-
- def __repr__(self):
- return '<fileview [%d, %d] %r>' % (
- self._start, self._end, self._fileobj)
-
- def tell(self):
- return self._pos
-
- def _checkwindow(self, seekto, op):
- if not (self._start <= seekto <= self._end):
- raise IOError("%s to offset %d is outside window [%d, %d]" % (
- op, seekto, self._start, self._end))
-
- def seek(self, offset, whence=0):
- seekto = offset
- if whence == os.SEEK_SET:
- seekto += self._start
- elif whence == os.SEEK_CUR:
- seekto += self._start + self._pos
- elif whence == os.SEEK_END:
- seekto += self._end
- else:
- raise IOError("Invalid whence argument to seek: %r" % (whence,))
- self._checkwindow(seekto, 'seek')
- self._fileobj.seek(seekto)
- self._pos = seekto - self._start
-
- def write(self, bytes):
- here = self._start + self._pos
- self._checkwindow(here, 'write')
- self._checkwindow(here + len(bytes), 'write')
- self._fileobj.seek(here, os.SEEK_SET)
- self._fileobj.write(bytes)
- self._pos += len(bytes)
-
- def read(self, size=maxint):
- assert size >= 0
- here = self._start + self._pos
- self._checkwindow(here, 'read')
- size = min(size, self._end - here)
- self._fileobj.seek(here, os.SEEK_SET)
- bytes = self._fileobj.read(size)
- self._pos += len(bytes)
- return 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(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)
- cputype, cpusubtype, filetype, ncmds, sizeofcmds, 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 ncmds commands
- for n in range(ncmds):
- where = file.tell()
- # Read command header
- cmd, cmdsize = 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(cmdsize - 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() + '\0'.encode())
- # Seek to the next command
- file.seek(where + cmdsize, 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
- nfat_arch = read_data(file, BIG_ENDIAN)
- for n in range(nfat_arch):
- # Read arch header
- cputype, cpusubtype, 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))
- do_file(open(path, 'r+b'))
-
-
-if __name__ == '__main__':
- main()
-
-## TODO:
-## Copy python.exe.manifest
-## Monkeypatch distutils.sysconfig